56 Commits
0.2.0 ... 0.4.0

Author SHA1 Message Date
Jonathan Lamothe
c7849d3558 version 0.4.0 2019-10-17 09:59:20 -04:00
Jonathan Lamothe
756d0997a8 updated change log 2019-10-16 02:40:09 -04:00
Jonathan Lamothe
569f009dcd Merge pull request #26 from mtlstats/ytd-stats
year-to-date statistics
2019-10-16 02:37:50 -04:00
Jonathan Lamothe
cfe2969106 generate empty game stats report on failure 2019-10-16 02:32:57 -04:00
Jonathan Lamothe
19e0242135 fixed name column spacing 2019-10-16 02:26:42 -04:00
Jonathan Lamothe
32f61ccc89 implemented year-to-date report 2019-10-16 02:23:52 -04:00
Jonathan Lamothe
bfe568492d implemented playerReport
a private function in the Mtlstats.Report module
2019-10-16 02:23:52 -04:00
Jonathan Lamothe
277ba9a9dd implemented playerNameColWidth 2019-10-15 01:03:32 -04:00
Jonathan Lamothe
d338930800 implemented playerIsActive 2019-10-15 00:51:42 -04:00
Jonathan Lamothe
363d0cb2d3 don't scroll past top of page 2019-10-15 00:16:44 -04:00
Jonathan Lamothe
a91ed5afb3 enable scrolling of report 2019-10-11 23:13:00 -04:00
Jonathan Lamothe
db8bbd9786 added scrollOffset field to ProgState 2019-10-11 22:24:27 -04:00
Jonathan Lamothe
c4f68bb29c Merge pull request #25 from mtlstats/pmin-prompt
Prompt user for penalty minutes and assign
2019-10-11 01:17:43 -04:00
Jonathan Lamothe
e2c3b57749 implemented assignPMins 2019-10-11 01:10:50 -04:00
Jonathan Lamothe
3d1f6170f6 implemented assignPMinsPrompt 2019-10-09 22:33:48 -04:00
Jonathan Lamothe
1a481ab49d implemented getPMinsC 2019-10-09 22:24:30 -04:00
Jonathan Lamothe
afd2bac7b5 implemented pMinPlayerPrompt 2019-10-09 21:54:55 -04:00
Jonathan Lamothe
ffe9b7f87f implemented pMinPlayerC 2019-10-09 01:24:55 -04:00
Jonathan Lamothe
e1a48afc5c penalty minutes control framework 2019-10-09 00:58:49 -04:00
Jonathan Lamothe
1810434716 added selectedPlayer and pMinsRecorded fields to GameState 2019-10-09 00:50:10 -04:00
Jonathan Lamothe
146e2e42a1 Merge pull request #24 from mtlstats/gs-totals
Calculate and display total player stats for game
2019-10-09 00:39:58 -04:00
Jonathan Lamothe
a9c036f876 renamed pPoints to psPoints 2019-10-09 00:35:35 -04:00
Jonathan Lamothe
0b249bcdae calculate and display total game stats 2019-10-09 00:30:03 -04:00
Jonathan Lamothe
74fd4fe2fb implemented addPlayerStats 2019-10-09 00:24:34 -04:00
Jonathan Lamothe
5f53413ef7 split report into standings and game stats 2019-10-09 00:01:12 -04:00
Jonathan Lamothe
83f951f7e4 version 0.3.0 2019-10-03 09:29:34 -04:00
Jonathan Lamothe
54a631557e Merge pull request #23 from mtlstats/game-stats
Show game statistics in report
2019-10-03 03:15:22 -04:00
Jonathan Lamothe
8424d5f40c add game stats to report 2019-10-03 03:08:17 -04:00
Jonathan Lamothe
de7f3f7a3e update recordGoalAssists test 2019-10-03 02:29:12 -04:00
Jonathan Lamothe
9d04abecff update awardAssist 2019-10-03 02:28:16 -04:00
Jonathan Lamothe
ad840cca65 awardGoal updates game stats 2019-10-03 02:10:22 -04:00
Jonathan Lamothe
b17e63246f added gamePlayerStats field to GameState 2019-10-03 01:10:44 -04:00
Jonathan Lamothe
9977a73da4 Merge pull request #22 from mtlstats/confirm-ga
Confirm goal/assist data
2019-10-02 02:09:07 -04:00
Jonathan Lamothe
0aa2b49ba2 fixed formatting of goal data confirmation prompt 2019-10-02 01:57:58 -04:00
Jonathan Lamothe
7da4c54e65 implemented resetGoalData 2019-10-02 01:55:07 -04:00
Jonathan Lamothe
4f70c84c6b implemented playerSummary 2019-10-02 01:31:07 -04:00
Jonathan Lamothe
2be7d2bf1d implemented confirmGoalDataC 2019-10-01 01:02:12 -04:00
Jonathan Lamothe
66148a25d8 don't automatically update goal/assist stats 2019-10-01 01:02:12 -04:00
Jonathan Lamothe
08c3382fe8 recordGoalAssists should clear confirmGoalDataFlag 2019-10-01 00:06:54 -04:00
Jonathan Lamothe
739db189ae added confirmGoalDataFlag field to GameState 2019-10-01 00:02:49 -04:00
Jonathan Lamothe
fc20259a48 Merge pull request #21 from mtlstats/refactor
Refactor
2019-09-28 02:17:13 -04:00
Jonathan Lamothe
b8a3af11a1 reference goals and assists by index number, not names 2019-09-28 02:09:11 -04:00
Jonathan Lamothe
ac92182b20 insert players at end of list (preserve index numbers) 2019-09-28 01:46:28 -04:00
Jonathan Lamothe
767c9b9221 Merge pull request #20 from mtlstats/assists
Assists
2019-09-27 01:46:06 -04:00
Jonathan Lamothe
669c854f4f implemented awardGoalAssists 2019-09-27 01:39:50 -04:00
Jonathan Lamothe
11fcbfcbdd implemented awardAssist 2019-09-26 02:07:55 -04:00
Jonathan Lamothe
c7c267b2a1 pressing enter without input results in player search failure 2019-09-26 01:36:10 -04:00
Jonathan Lamothe
75803edfe7 implemented assist prompt 2019-09-26 01:23:34 -04:00
Jonathan Lamothe
ffdb8e1e8c framework for recording assists 2019-09-25 02:44:42 -04:00
Jonathan Lamothe
8c8a2d52a6 implemented modifyPlayer 2019-09-25 02:42:37 -04:00
Jonathan Lamothe
625d9c616a added goalBy and assistsBy to GameState 2019-09-25 02:42:37 -04:00
Jonathan Lamothe
fc58b0a72b added maxAssists config value 2019-09-25 01:30:28 -04:00
Jonathan Lamothe
c9b822df3c Merge pull request #19 from mtlstats/gfga
Calculate goals for/goals against
2019-09-21 00:43:35 -04:00
Jonathan Lamothe
a9918c559b update report to contain goals for and goals against 2019-09-21 00:35:46 -04:00
Jonathan Lamothe
06c94260ad update goals for and goals against when updating game stats 2019-09-21 00:31:40 -04:00
Jonathan Lamothe
9f68d0da1d added gmsGoalsFor and gmsGoalsAgainst fields to GameStats 2019-09-21 00:03:33 -04:00
13 changed files with 1059 additions and 197 deletions

View File

@@ -1,5 +1,16 @@
# Changelog for mtlstats # Changelog for mtlstats
## v0.4.0
- Record penalty minutes
- Calculate total game statistics
- Generate year-to-date statistics report
## v0.3.0
- Record goals and assists
- Track goals for and goals against
## v0.2.0 ## v0.2.0
- Overtime losses don't count in the loss column - Overtime losses don't count in the loss column

View File

@@ -1,5 +1,5 @@
name: mtlstats name: mtlstats
version: 0.2.0 version: 0.4.0
github: "mtlstats/mtlstats" github: "mtlstats/mtlstats"
license: GPL-3 license: GPL-3
author: "Jonathan Lamothe" author: "Jonathan Lamothe"
@@ -22,6 +22,7 @@ description: Please see the README on GitHub at <https://github.com/jlam
dependencies: dependencies:
- base >= 4.7 && < 5 - base >= 4.7 && < 5
- aeson >= 1.4.4.0 && < 1.5 - aeson >= 1.4.4.0 && < 1.5
- containers >= 0.6.0.1 && < 0.7
- easy-file >= 0.2.2 && < 0.3 - easy-file >= 0.2.2 && < 0.3
- extra >= 1.6.17 && < 1.7 - extra >= 1.6.17 && < 1.7
- microlens-th >= 0.4.2.3 && < 0.5 - microlens-th >= 0.4.2.3 && < 0.5

View File

@@ -32,15 +32,24 @@ module Mtlstats.Actions
, validateGameDate , validateGameDate
, createPlayer , createPlayer
, addPlayer , addPlayer
, recordGoalAssists
, awardGoal , awardGoal
, awardAssist
, resetGoalData
, assignPMins
, backHome
, scrollUp
, scrollDown
) where ) where
import Control.Monad.Trans.State (modify) import Control.Monad.Trans.State (modify)
import qualified Data.Map as M
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Data.Time.Calendar (fromGregorianValid) import Data.Time.Calendar (fromGregorianValid)
import Lens.Micro (over, (^.), (&), (.~), (?~), (%~), (+~)) import Lens.Micro (over, (^.), (&), (.~), (?~), (%~), (+~))
import Mtlstats.Types import Mtlstats.Types
import Mtlstats.Util
-- | Starts a new season -- | Starts a new season
startNewSeason :: ProgState -> ProgState startNewSeason :: ProgState -> ProgState
@@ -82,26 +91,37 @@ overtimeCheck s
-- | Adjusts the game stats based on the results of the current game -- | Adjusts the game stats based on the results of the current game
updateGameStats :: ProgState -> ProgState updateGameStats :: ProgState -> ProgState
updateGameStats s = fromMaybe s $ do updateGameStats s = fromMaybe s $ do
gType <- s^.progMode.gameStateL.gameType let gs = s^.progMode.gameStateL
won <- gameWon $ s^.progMode.gameStateL gType <- gs^.gameType
lost <- gameLost $ s^.progMode.gameStateL won <- gameWon gs
ot <- s^.progMode.gameStateL.overtimeFlag lost <- gameLost gs
ot <- gs^.overtimeFlag
tScore <- teamScore gs
oScore <- otherScore gs
let let
hw = if gType == HomeGame && won then 1 else 0 hw = if gType == HomeGame && won then 1 else 0
hl = if gType == HomeGame && lost then 1 else 0 hl = if gType == HomeGame && lost then 1 else 0
hot = if gType == HomeGame && ot then 1 else 0 hot = if gType == HomeGame && ot then 1 else 0
hgf = if gType == HomeGame then tScore else 0
hga = if gType == HomeGame then oScore else 0
aw = if gType == AwayGame && won then 1 else 0 aw = if gType == AwayGame && won then 1 else 0
al = if gType == AwayGame && lost then 1 else 0 al = if gType == AwayGame && lost then 1 else 0
aot = if gType == AwayGame && ot then 1 else 0 aot = if gType == AwayGame && ot then 1 else 0
agf = if gType == AwayGame then tScore else 0
aga = if gType == AwayGame then oScore else 0
Just $ s Just $ s
& database.dbHomeGameStats & database.dbHomeGameStats
%~ (gmsWins +~ hw) %~ (gmsWins +~ hw)
. (gmsLosses +~ hl) . (gmsLosses +~ hl)
. (gmsOvertime +~ hot) . (gmsOvertime +~ hot)
. (gmsGoalsFor +~ hgf)
. (gmsGoalsAgainst +~ hga)
& database.dbAwayGameStats & database.dbAwayGameStats
%~ (gmsWins +~ aw) %~ (gmsWins +~ aw)
. (gmsLosses +~ al) . (gmsLosses +~ al)
. (gmsOvertime +~ aot) . (gmsOvertime +~ aot)
. (gmsGoalsFor +~ agf)
. (gmsGoalsAgainst +~ aga)
-- | Validates the game date -- | Validates the game date
validateGameDate :: ProgState -> ProgState validateGameDate :: ProgState -> ProgState
@@ -136,7 +156,22 @@ addPlayer s = fromMaybe s $ do
pos = cps^.cpsPosition pos = cps^.cpsPosition
player = newPlayer num name pos player = newPlayer num name pos
Just $ s & database.dbPlayers Just $ s & database.dbPlayers
%~ (player:) %~ (++[player])
-- | Awards the goal and assists to the players
recordGoalAssists :: ProgState -> ProgState
recordGoalAssists ps = fromMaybe ps $ do
let gs = ps^.progMode.gameStateL
goalId <- gs^.goalBy
let assistIds = gs^.assistsBy
Just $ ps
& awardGoal goalId
& (\s -> foldr awardAssist s assistIds)
& progMode.gameStateL
%~ (goalBy .~ Nothing)
. (assistsBy .~ [])
. (pointsAccounted %~ succ)
. (confirmGoalDataFlag .~ False)
-- | Awards a goal to a player -- | Awards a goal to a player
awardGoal awardGoal
@@ -145,10 +180,70 @@ awardGoal
-> ProgState -> ProgState
-> ProgState -> ProgState
awardGoal n ps = ps awardGoal n ps = ps
& database.dbPlayers & progMode.gameStateL.gamePlayerStats %~
%~ map (\m -> let
(\(i, p) -> if i == n stats = M.findWithDefault newPlayerStats n m
then p in M.insert n (stats & psGoals %~ succ) m)
& pYtd.psGoals %~ succ & database.dbPlayers %~ map
& pLifetime.psGoals %~ succ (\(i, p) -> if i == n
else p) . zip [0..] then p
& pYtd.psGoals %~ succ
& pLifetime.psGoals %~ succ
else p) . zip [0..]
-- | Awards an assist to a player
awardAssist
:: Int
-- ^ The player's index number
-> ProgState
-> ProgState
awardAssist n ps = ps
& progMode.gameStateL.gamePlayerStats %~
(\m -> let
stats = M.findWithDefault newPlayerStats n m
in M.insert n (stats & psAssists %~ succ) m)
& database.dbPlayers %~ map
(\(i, p) -> if i == n
then p
& pYtd.psAssists %~ succ
& pLifetime.psAssists %~ succ
else p) . zip [0..]
-- | Resets the entered data for the current goal
resetGoalData :: ProgState -> ProgState
resetGoalData ps = ps & progMode.gameStateL
%~ (goalBy .~ Nothing)
. (assistsBy .~ [])
. (confirmGoalDataFlag .~ False)
-- | Adds penalty minutes to a player
assignPMins
:: Int
-- ^ The number of minutes to add
-> ProgState
-> ProgState
assignPMins mins s = fromMaybe s $ do
n <- s^.progMode.gameStateL.selectedPlayer
Just $ s
& database.dbPlayers %~ modifyNth n
(((pYtd.psPMin) +~ mins) . ((pLifetime.psPMin) +~ mins))
& progMode.gameStateL
%~ ( gamePlayerStats %~ updateMap n newPlayerStats
(psPMin +~ mins)
)
. (selectedPlayer .~ Nothing)
-- | Resets the program state back to the main menu
backHome :: ProgState -> ProgState
backHome
= (progMode .~ MainMenu)
. (inputBuffer .~ "")
. (scrollOffset .~ 0)
-- | Scrolls the display up
scrollUp :: ProgState -> ProgState
scrollUp = scrollOffset %~ max 0 . pred
-- | Scrolls the display down
scrollDown :: ProgState -> ProgState
scrollDown = scrollOffset %~ succ

View File

@@ -36,3 +36,7 @@ appName = "mtlstats"
-- | The database filename -- | The database filename
dbFname :: String dbFname :: String
dbFname = "database.json" dbFname = "database.json"
-- | The maximum number of assists
maxAssists :: Int
maxAssists = 2

View File

@@ -24,8 +24,8 @@ module Mtlstats.Control (dispatch) where
import Control.Monad (join, when) import Control.Monad (join, when)
import Control.Monad.Trans.State (gets, modify) import Control.Monad.Trans.State (gets, modify)
import Data.Char (toUpper) import Data.Char (toUpper)
import Data.Maybe (fromJust) import Data.Maybe (fromJust, fromMaybe, isJust)
import Lens.Micro ((^.), (.~)) import Lens.Micro ((^.), (.~), (%~))
import Lens.Micro.Extras (view) import Lens.Micro.Extras (view)
import qualified UI.NCurses as C import qualified UI.NCurses as C
@@ -36,6 +36,7 @@ import Mtlstats.Menu
import Mtlstats.Prompt import Mtlstats.Prompt
import Mtlstats.Report import Mtlstats.Report
import Mtlstats.Types import Mtlstats.Types
import Mtlstats.Util
-- | Reads the program state and returs the apropriate controller to -- | Reads the program state and returs the apropriate controller to
-- run -- run
@@ -53,7 +54,9 @@ dispatch s = case s^.progMode of
| null $ gs^.awayScore -> awayScoreC | null $ gs^.awayScore -> awayScoreC
| null $ gs^.overtimeFlag -> overtimeFlagC | null $ gs^.overtimeFlag -> overtimeFlagC
| not $ gs^.dataVerified -> verifyDataC | not $ gs^.dataVerified -> verifyDataC
| fromJust (unaccountedPoints gs) -> recordGoalC | fromJust (unaccountedPoints gs) -> goalInput gs
| isJust $ gs^.selectedPlayer -> getPMinsC
| not $ gs^.pMinsRecorded -> pMinPlayerC
| otherwise -> reportC | otherwise -> reportC
CreatePlayer cps CreatePlayer cps
| null $ cps^.cpsNumber -> getPlayerNumC | null $ cps^.cpsNumber -> getPlayerNumC
@@ -182,32 +185,105 @@ verifyDataC = Controller
return True return True
} }
goalInput :: GameState -> Controller
goalInput gs
| null (gs^.goalBy ) = recordGoalC
| not (gs^.confirmGoalDataFlag) = recordAssistC
| otherwise = confirmGoalDataC
recordGoalC :: Controller recordGoalC :: Controller
recordGoalC = Controller recordGoalC = Controller
{ drawController = \s -> let { drawController = \s -> let
game = s^.database.dbGames (game, goal) = gameGoal s
goal = succ $ s^.progMode.gameStateL.pointsAccounted
in drawPrompt (recordGoalPrompt game goal) s in drawPrompt (recordGoalPrompt game goal) s
, handleController = \e -> do , handleController = \e -> do
game <- gets $ view $ database.dbGames (game, goal) <- gets gameGoal
goal <- succ <$> gets (view $ progMode.gameStateL.pointsAccounted)
promptHandler (recordGoalPrompt game goal) e promptHandler (recordGoalPrompt game goal) e
return True return True
} }
recordAssistC :: Controller
recordAssistC = Controller
{ drawController = \s -> let
(game, goal, assist) = gameGoalAssist s
in drawPrompt (recordAssistPrompt game goal assist) s
, handleController = \e -> do
(game, goal, assist) <- gets gameGoalAssist
promptHandler (recordAssistPrompt game goal assist) e
return True
}
confirmGoalDataC :: Controller
confirmGoalDataC = Controller
{ drawController = \s -> do
let
(game, goal) = gameGoal s
gs = s^.progMode.gameStateL
players = s^.database.dbPlayers
msg = unlines $
[ " Game: " ++ padNum 2 game
, " Goal: " ++ show goal
, "Goal scored by: " ++
playerSummary (fromJust $ gs^.goalBy >>= flip nth players)
] ++
map
(\pid -> " Assisted by: " ++
playerSummary (fromJust $ nth pid players))
(gs^.assistsBy) ++
[ ""
, "Is the above information correct? (Y/N)"
]
C.drawString msg
return C.CursorInvisible
, handleController = \e -> do
case ynHandler e of
Just True -> modify recordGoalAssists
Just False -> modify resetGoalData
Nothing -> return ()
return True
}
pMinPlayerC :: Controller
pMinPlayerC = Controller
{ drawController = \s -> do
header s
drawPrompt pMinPlayerPrompt s
, handleController = \e -> do
promptHandler pMinPlayerPrompt e
return True
}
getPMinsC :: Controller
getPMinsC = Controller
{ drawController = \s -> do
header s
C.drawString $ fromMaybe "" $ do
pid <- s^.progMode.gameStateL.selectedPlayer
player <- nth pid $ s^.database.dbPlayers
Just $ playerSummary player ++ "\n"
drawPrompt assignPMinsPrompt s
, handleController = \e -> do
promptHandler assignPMinsPrompt e
return True
}
reportC :: Controller reportC :: Controller
reportC = Controller reportC = Controller
{ drawController = \s -> do { drawController = \s -> do
(_, cols) <- C.windowSize (rows, cols) <- C.windowSize
C.drawString $ report (fromInteger $ pred cols) s C.drawString $ unlines $ slice
(s^.scrollOffset)
(fromInteger $ pred rows)
(report (fromInteger $ pred cols) s)
return C.CursorInvisible return C.CursorInvisible
, handleController = \e -> do , handleController = \e -> do
when case e of
(case e of C.EventSpecialKey C.KeyUpArrow -> modify scrollUp
C.EventCharacter _ -> True C.EventSpecialKey C.KeyDownArrow -> modify scrollDown
C.EventSpecialKey _ -> True C.EventSpecialKey C.KeyHome -> modify $ scrollOffset .~ 0
_ -> False) $ C.EventSpecialKey _ -> modify backHome
modify $ progMode .~ MainMenu C.EventCharacter _ -> modify backHome
_ -> return ()
return True return True
} }
@@ -258,3 +334,15 @@ confirmCreatePlayerC = Controller
Nothing -> return () Nothing -> return ()
return True return True
} }
gameGoal :: ProgState -> (Int, Int)
gameGoal s =
( s^.database.dbGames
, succ $ s^.progMode.gameStateL.pointsAccounted
)
gameGoalAssist :: ProgState -> (Int, Int, Int)
gameGoalAssist s = let
(game, goal) = gameGoal s
assist = succ $ length $ s^.progMode.gameStateL.assistsBy
in (game, goal, assist)

View File

@@ -38,6 +38,9 @@ module Mtlstats.Prompt (
playerPosPrompt, playerPosPrompt,
selectPlayerPrompt, selectPlayerPrompt,
recordGoalPrompt, recordGoalPrompt,
recordAssistPrompt,
pMinPlayerPrompt,
assignPMinsPrompt
) where ) where
import Control.Monad (when) import Control.Monad (when)
@@ -167,23 +170,26 @@ selectPlayerPrompt pStr callback = Prompt
sel sel
C.moveCursor row col C.moveCursor row col
, promptCharCheck = const True , promptCharCheck = const True
, promptAction = \sStr -> do , promptAction = \sStr -> if null sStr
players <- gets $ view $ database.dbPlayers then callback Nothing
case playerSearchExact sStr players of else do
Just (n, _) -> callback $ Just n players <- gets $ view $ database.dbPlayers
Nothing -> do case playerSearchExact sStr players of
mode <- gets $ view progMode Just (n, _) -> callback $ Just n
let Nothing -> do
cps mode <- gets $ view progMode
= newCreatePlayerState let
& cpsName .~ sStr cps
& cpsSuccessCallback .~ do = newCreatePlayerState
modify $ progMode .~ mode & cpsName .~ sStr
callback (Just 0) & cpsSuccessCallback .~ do
& cpsFailureCallback .~ do modify $ progMode .~ mode
modify $ progMode .~ mode pIndex <- pred . length <$> gets (view $ database.dbPlayers)
callback Nothing callback $ Just pIndex
modify $ progMode .~ CreatePlayer cps & cpsFailureCallback .~ do
modify $ progMode .~ mode
callback Nothing
modify $ progMode .~ CreatePlayer cps
, promptSpecialKey = \case , promptSpecialKey = \case
C.KeyFunction n -> do C.KeyFunction n -> do
sStr <- gets $ view inputBuffer sStr <- gets $ view inputBuffer
@@ -205,13 +211,41 @@ recordGoalPrompt
-- ^ The goal number -- ^ The goal number
-> Prompt -> Prompt
recordGoalPrompt game goal = selectPlayerPrompt recordGoalPrompt game goal = selectPlayerPrompt
("*** GAME " ++ padNum 2 game ++ " ***\n" ++ ( "*** GAME " ++ padNum 2 game ++ " ***\n"
"Who scored goal number " ++ show goal ++ "? ") $ ++ "Who scored goal number " ++ show goal ++ "? "
) $ modify . (progMode.gameStateL.goalBy .~)
-- | Prompts for a player who assisted the goal
recordAssistPrompt
:: Int
-- ^ The game number
-> Int
-- ^ The goal nuber
-> Int
-- ^ The assist number
-> Prompt
recordAssistPrompt game goal assist = selectPlayerPrompt
( "*** GAME " ++ padNum 2 game ++ " ***\n"
++ "Goal: " ++ show goal ++ "\n"
++ "Assist #" ++ show assist ++ ": "
) $ \case
Nothing -> modify $ progMode.gameStateL.confirmGoalDataFlag .~ True
Just n -> do
modify $ progMode.gameStateL.assistsBy %~ (++[n])
nAssists <- length <$> gets (view $ progMode.gameStateL.assistsBy)
when (nAssists >= maxAssists) $
modify $ progMode.gameStateL.confirmGoalDataFlag .~ True
pMinPlayerPrompt :: Prompt
pMinPlayerPrompt = selectPlayerPrompt
"Assign penalty minutes to: " $
\case \case
Nothing -> return () Nothing -> modify $ progMode.gameStateL.pMinsRecorded .~ True
Just n -> modify Just n -> modify $ progMode.gameStateL.selectedPlayer ?~ n
$ awardGoal n
. (progMode.gameStateL.pointsAccounted %~ succ) assignPMinsPrompt :: Prompt
assignPMinsPrompt = numPrompt "Penalty minutes: " $
modify . assignPMins
drawSimplePrompt :: String -> ProgState -> C.Update () drawSimplePrompt :: String -> ProgState -> C.Update ()
drawSimplePrompt pStr s = C.drawString $ pStr ++ s^.inputBuffer drawSimplePrompt pStr s = C.drawString $ pStr ++ s^.inputBuffer

View File

@@ -19,14 +19,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
-} -}
module Mtlstats.Report (report, gameDate) where module Mtlstats.Report (report, gameDate, playerNameColWidth) where
import qualified Data.Map as M
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Lens.Micro ((^.)) import Lens.Micro ((^.))
import Mtlstats.Config import Mtlstats.Config
import Mtlstats.Format import Mtlstats.Format
import Mtlstats.Types import Mtlstats.Types
import Mtlstats.Util
-- | Generates the report -- | Generates the report
report report
@@ -34,8 +36,16 @@ report
-- ^ The number of columns for the report -- ^ The number of columns for the report
-> ProgState -> ProgState
-- ^ The program state -- ^ The program state
-> String -> [String]
report width s = unlines $ fromMaybe [] $ do report width s
= standingsReport width s
++ [""]
++ gameStatsReport width s
++ [""]
++ yearToDateStatsReport width s
standingsReport :: Int -> ProgState -> [String]
standingsReport width s = fromMaybe [] $ do
let let
db = s^.database db = s^.database
gs = s^.progMode.gameStateL gs = s^.progMode.gameStateL
@@ -63,6 +73,8 @@ report width s = unlines $ fromMaybe [] $ do
++ right 4 "W" ++ right 4 "W"
++ right 4 "L" ++ right 4 "L"
++ right 4 "OT" ++ right 4 "OT"
++ right 4 "GF"
++ right 4 "GA"
++ right 4 "P" ++ right 4 "P"
, centre width , centre width
$ left 11 "HOME" $ left 11 "HOME"
@@ -72,12 +84,25 @@ report width s = unlines $ fromMaybe [] $ do
++ showStats aStats ++ showStats aStats
, centre width , centre width
$ replicate 11 ' ' $ replicate 11 ' '
++ replicate (2 + 4 * 4) '-' ++ replicate (2 + 4 * 6) '-'
, centre width , centre width
$ left 11 "TOTALS" $ left 11 "TOTALS"
++ showStats tStats ++ showStats tStats
] ]
gameStatsReport :: Int -> ProgState -> [String]
gameStatsReport width s = playerReport width "GAME" $
fromMaybe [] $ mapM
(\(pid, stats) -> do
p <- nth pid $ s^.database.dbPlayers
Just (p, stats))
(M.toList $ s^.progMode.gameStateL.gamePlayerStats)
yearToDateStatsReport :: Int -> ProgState -> [String]
yearToDateStatsReport width s = playerReport width "YEAR TO DATE" $
map (\p -> (p, p^.pYtd)) $
filter playerIsActive $ s^.database.dbPlayers
gameDate :: GameState -> String gameDate :: GameState -> String
gameDate gs = fromMaybe "" $ do gameDate gs = fromMaybe "" $ do
year <- show <$> gs^.gameYear year <- show <$> gs^.gameYear
@@ -85,10 +110,55 @@ gameDate gs = fromMaybe "" $ do
day <- padNum 2 <$> gs^.gameDay day <- padNum 2 <$> gs^.gameDay
Just $ month ++ " " ++ day ++ " " ++ year Just $ month ++ " " ++ day ++ " " ++ year
playerReport :: Int -> String -> [(Player, PlayerStats)] -> [String]
playerReport width label ps = let
nameWidth = playerNameColWidth $ map fst ps
tStats = foldr (addPlayerStats . snd) newPlayerStats ps
in
[ centre width (label ++ " STATISTICS")
, ""
, centre width
$ "NO. "
++ left nameWidth "PLAYER"
++ right 3 "G"
++ right 6 "A"
++ right 6 "P"
++ right 6 "PM"
] ++ map
(\(p, stats) -> centre width
$ right 2 (show $ p^.pNumber)
++ " "
++ left nameWidth (p^.pName)
++ right 3 (show $ stats^.psGoals)
++ right 6 (show $ stats^.psAssists)
++ right 6 (show $ psPoints stats)
++ right 6 (show $ stats^.psPMin))
ps ++
[ centre width
$ replicate (4 + nameWidth) ' '
++ replicate (3 + 3 * 6) '-'
, overlay
(label ++ " TOTALS")
( centre width
$ replicate (4 + nameWidth) ' '
++ right 3 (show $ tStats^.psGoals)
++ right 6 (show $ tStats^.psAssists)
++ right 6 (show $ psPoints tStats)
++ right 6 (show $ tStats^.psPMin)
)
]
playerNameColWidth :: [Player] -> Int
playerNameColWidth = foldr
(\player current -> max current $ succ $ length $ player^.pName)
10
showStats :: GameStats -> String showStats :: GameStats -> String
showStats gs showStats gs
= right 2 (show $ gmsGames gs) = right 2 (show $ gmsGames gs)
++ right 4 (show $ gs^.gmsWins) ++ right 4 (show $ gs^.gmsWins)
++ right 4 (show $ gs^.gmsLosses) ++ right 4 (show $ gs^.gmsLosses)
++ right 4 (show $ gs^.gmsOvertime) ++ right 4 (show $ gs^.gmsOvertime)
++ right 4 (show $ gs^.gmsGoalsFor)
++ right 4 (show $ gs^.gmsGoalsAgainst)
++ right 4 (show $ gmsPoints gs) ++ right 4 (show $ gmsPoints gs)

View File

@@ -42,6 +42,7 @@ module Mtlstats.Types (
database, database,
progMode, progMode,
inputBuffer, inputBuffer,
scrollOffset,
-- ** ProgMode Lenses -- ** ProgMode Lenses
gameStateL, gameStateL,
createPlayerStateL, createPlayerStateL,
@@ -56,6 +57,12 @@ module Mtlstats.Types (
overtimeFlag, overtimeFlag,
dataVerified, dataVerified,
pointsAccounted, pointsAccounted,
goalBy,
assistsBy,
gamePlayerStats,
confirmGoalDataFlag,
selectedPlayer,
pMinsRecorded,
-- ** CreatePlayerState Lenses -- ** CreatePlayerState Lenses
cpsNumber, cpsNumber,
cpsName, cpsName,
@@ -95,6 +102,8 @@ module Mtlstats.Types (
gmsWins, gmsWins,
gmsLosses, gmsLosses,
gmsOvertime, gmsOvertime,
gmsGoalsFor,
gmsGoalsAgainst,
-- * Constructors -- * Constructors
newProgState, newProgState,
newGameState, newGameState,
@@ -120,9 +129,14 @@ module Mtlstats.Types (
gmsPoints, gmsPoints,
addGameStats, addGameStats,
-- ** Player Helpers -- ** Player Helpers
pPoints,
playerSearch, playerSearch,
playerSearchExact playerSearchExact,
modifyPlayer,
playerSummary,
playerIsActive,
-- ** PlayerStats Helpers
psPoints,
addPlayerStats
) where ) where
import Control.Monad.Trans.State (StateT) import Control.Monad.Trans.State (StateT)
@@ -139,6 +153,7 @@ import Data.Aeson
, (.=) , (.=)
) )
import Data.List (isInfixOf) import Data.List (isInfixOf)
import qualified Data.Map as M
import Data.Maybe (listToMaybe) import Data.Maybe (listToMaybe)
import Lens.Micro (Lens', lens, (&), (^.), (.~)) import Lens.Micro (Lens', lens, (&), (^.), (.~))
import Lens.Micro.TH (makeLenses) import Lens.Micro.TH (makeLenses)
@@ -159,12 +174,14 @@ type Action a = StateT ProgState C.Curses a
-- | Represents the program state -- | Represents the program state
data ProgState = ProgState data ProgState = ProgState
{ _database :: Database { _database :: Database
-- ^ The data to be saved -- ^ The data to be saved
, _progMode :: ProgMode , _progMode :: ProgMode
-- ^ The program's mode -- ^ The program's mode
, _inputBuffer :: String , _inputBuffer :: String
-- ^ Buffer for user input -- ^ Buffer for user input
, _scrollOffset :: Int
-- ^ The scrolling offset for the display
} }
-- | The program mode -- | The program mode
@@ -182,25 +199,40 @@ instance Show ProgMode where
-- | The game state -- | The game state
data GameState = GameState data GameState = GameState
{ _gameYear :: Maybe Int { _gameYear :: Maybe Int
-- ^ The year the game took place -- ^ The year the game took place
, _gameMonth :: Maybe Int , _gameMonth :: Maybe Int
-- ^ The month the game took place -- ^ The month the game took place
, _gameDay :: Maybe Int , _gameDay :: Maybe Int
-- ^ The day of the month the game took place -- ^ The day of the month the game took place
, _gameType :: Maybe GameType , _gameType :: Maybe GameType
-- ^ The type of game (home/away) -- ^ The type of game (home/away)
, _otherTeam :: String , _otherTeam :: String
-- ^ The name of the other team -- ^ The name of the other team
, _homeScore :: Maybe Int , _homeScore :: Maybe Int
-- ^ The home team's score -- ^ The home team's score
, _awayScore :: Maybe Int , _awayScore :: Maybe Int
-- ^ The away team's score -- ^ The away team's score
, _overtimeFlag :: Maybe Bool , _overtimeFlag :: Maybe Bool
-- ^ Indicates whether or not the game went into overtime -- ^ Indicates whether or not the game went into overtime
, _dataVerified :: Bool , _dataVerified :: Bool
-- ^ Set to 'True' when the user confirms the entered data -- ^ Set to 'True' when the user confirms the entered data
, _pointsAccounted :: Int , _pointsAccounted :: Int
-- ^ The number of game points accounted for
, _goalBy :: Maybe Int
-- ^ The index number of the player who scored the most recently
-- entered goal
, _assistsBy :: [Int]
-- ^ The index numbers of the players who have assisted the most
-- recently entered goal
, _gamePlayerStats :: M.Map Int PlayerStats
-- ^ The player stats accumulated over the game
, _confirmGoalDataFlag :: Bool
-- ^ Set when the user confirms the goal data
, _selectedPlayer :: Maybe Int
-- ^ Index number of the selected 'Player'
, _pMinsRecorded :: Bool
-- ^ Set when the penalty mintes have been recorded
} deriving (Eq, Show) } deriving (Eq, Show)
-- | The type of game -- | The type of game
@@ -405,12 +437,16 @@ instance ToJSON GoalieStats where
-- | Game statistics -- | Game statistics
data GameStats = GameStats data GameStats = GameStats
{ _gmsWins :: Int { _gmsWins :: Int
-- ^ Games won -- ^ Games won
, _gmsLosses :: Int , _gmsLosses :: Int
-- ^ Games lost -- ^ Games lost
, _gmsOvertime :: Int , _gmsOvertime :: Int
-- ^ Games lost in overtime -- ^ Games lost in overtime
, _gmsGoalsFor :: Int
-- ^ Goals for the team
, _gmsGoalsAgainst :: Int
-- ^ Goals against the team
} deriving (Eq, Show) } deriving (Eq, Show)
instance FromJSON GameStats where instance FromJSON GameStats where
@@ -418,17 +454,23 @@ instance FromJSON GameStats where
<$> v .: "wins" <$> v .: "wins"
<*> v .: "losses" <*> v .: "losses"
<*> v .: "overtime" <*> v .: "overtime"
<*> v .: "goals_for"
<*> v .: "goals_against"
instance ToJSON GameStats where instance ToJSON GameStats where
toJSON (GameStats w l ot) = object toJSON (GameStats w l ot gf ga) = object
[ "wins" .= w [ "wins" .= w
, "losses" .= l , "losses" .= l
, "overtime" .= ot , "overtime" .= ot
, "goals_for" .= gf
, "goals_against" .= ga
] ]
toEncoding (GameStats w l ot) = pairs $ toEncoding (GameStats w l ot gf ga) = pairs $
"wins" .= w <> "wins" .= w <>
"losses" .= l <> "losses" .= l <>
"overtime" .= ot "overtime" .= ot <>
"goals_for" .= gf <>
"goals_against" .= ga
-- | Defines a user prompt -- | Defines a user prompt
data Prompt = Prompt data Prompt = Prompt
@@ -469,24 +511,31 @@ createPlayerStateL = lens
-- | Constructor for a 'ProgState' -- | Constructor for a 'ProgState'
newProgState :: ProgState newProgState :: ProgState
newProgState = ProgState newProgState = ProgState
{ _database = newDatabase { _database = newDatabase
, _progMode = MainMenu , _progMode = MainMenu
, _inputBuffer = "" , _inputBuffer = ""
, _scrollOffset = 0
} }
-- | Constructor for a 'GameState' -- | Constructor for a 'GameState'
newGameState :: GameState newGameState :: GameState
newGameState = GameState newGameState = GameState
{ _gameYear = Nothing { _gameYear = Nothing
, _gameMonth = Nothing , _gameMonth = Nothing
, _gameDay = Nothing , _gameDay = Nothing
, _gameType = Nothing , _gameType = Nothing
, _otherTeam = "" , _otherTeam = ""
, _homeScore = Nothing , _homeScore = Nothing
, _awayScore = Nothing , _awayScore = Nothing
, _overtimeFlag = Nothing , _overtimeFlag = Nothing
, _dataVerified = False , _dataVerified = False
, _pointsAccounted = 0 , _pointsAccounted = 0
, _goalBy = Nothing
, _assistsBy = []
, _gamePlayerStats = M.empty
, _confirmGoalDataFlag = False
, _selectedPlayer = Nothing
, _pMinsRecorded = False
} }
-- | Constructor for a 'CreatePlayerState' -- | Constructor for a 'CreatePlayerState'
@@ -563,9 +612,11 @@ newGoalieStats = GoalieStats
-- | Constructor for a 'GameStats' value -- | Constructor for a 'GameStats' value
newGameStats :: GameStats newGameStats :: GameStats
newGameStats = GameStats newGameStats = GameStats
{ _gmsWins = 0 { _gmsWins = 0
, _gmsLosses = 0 , _gmsLosses = 0
, _gmsOvertime = 0 , _gmsOvertime = 0
, _gmsGoalsFor = 0
, _gmsGoalsAgainst = 0
} }
-- | Determines the team's score -- | Determines the team's score
@@ -632,15 +683,13 @@ gmsPoints gs = 2 * gs^.gmsWins + gs^. gmsOvertime
-- | Adds two 'GameStats' values together -- | Adds two 'GameStats' values together
addGameStats :: GameStats -> GameStats -> GameStats addGameStats :: GameStats -> GameStats -> GameStats
addGameStats s1 s2 = GameStats addGameStats s1 s2 = GameStats
{ _gmsWins = s1^.gmsWins + s2^.gmsWins { _gmsWins = s1^.gmsWins + s2^.gmsWins
, _gmsLosses = s1^.gmsLosses + s2^.gmsLosses , _gmsLosses = s1^.gmsLosses + s2^.gmsLosses
, _gmsOvertime = s1^.gmsOvertime + s2^.gmsOvertime , _gmsOvertime = s1^.gmsOvertime + s2^.gmsOvertime
, _gmsGoalsFor = s1^.gmsGoalsFor + s2^.gmsGoalsFor
, _gmsGoalsAgainst = s1^.gmsGoalsAgainst + s2^.gmsGoalsAgainst
} }
-- | Calculates a player's points
pPoints :: PlayerStats -> Int
pPoints s = s^.psGoals + s^.psAssists
-- | Searches through a list of players -- | Searches through a list of players
playerSearch playerSearch
:: String :: String
@@ -667,3 +716,44 @@ playerSearchExact sStr =
filter (match sStr) . filter (match sStr) .
zip [0..] zip [0..]
where match sStr (_, p) = p^.pName == sStr where match sStr (_, p) = p^.pName == sStr
-- | Modifies a player with a given name
modifyPlayer
:: (Player -> Player)
-- ^ The modification function
-> String
-- ^ The player's name
-> [Player]
-- ^ The list of players to modify
-> [Player]
-- ^ The modified list
modifyPlayer f n = map
(\p -> if p^.pName == n
then f p
else p)
-- | Provides a short summary string for a player
playerSummary :: Player -> String
playerSummary p =
p^.pName ++ " (" ++ show (p^.pNumber) ++ ") " ++ p^.pPosition
-- | Determines whether or not a player has been active in the current
-- season/year
playerIsActive :: Player -> Bool
playerIsActive = do
stats <- (^.pYtd)
return
$ stats^.psGoals /= 0
|| stats^.psAssists /= 0
|| stats^.psPMin /= 0
-- | Calculates a player's points
psPoints :: PlayerStats -> Int
psPoints s = s^.psGoals + s^.psAssists
-- | Adds two 'PlayerStats' together
addPlayerStats :: PlayerStats -> PlayerStats -> PlayerStats
addPlayerStats s1 s2 = newPlayerStats
& psGoals .~ s1^.psGoals + s2^.psGoals
& psAssists .~ s1^.psAssists + s2^.psAssists
& psPMin .~ s1^.psPMin + s2^.psPMin

View File

@@ -19,11 +19,59 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
-} -}
module Mtlstats.Util (nth) where module Mtlstats.Util (nth, modifyNth, updateMap, slice) where
nth :: Int -> [a] -> Maybe a import qualified Data.Map as M
-- | Attempt to select the element from a list at a given index
nth
:: Int
-- ^ The index
-> [a]
-- ^ The list
-> Maybe a
nth _ [] = Nothing nth _ [] = Nothing
nth n (x:xs) nth n (x:xs)
| n == 0 = Just x | n == 0 = Just x
| n < 0 = Nothing | n < 0 = Nothing
| otherwise = nth (pred n) xs | otherwise = nth (pred n) xs
-- | Attempt to modify the index at a given index in a list
modifyNth
:: Int
-- ^ The index
-> (a -> a)
-- ^ The modification function
-> [a]
-- ^ The list
-> [a]
modifyNth n f = map (\(i, x) -> if i == n then f x else x)
. zip [0..]
-- | Modify a value indexed by a given key in a map using a default
-- initial value if not present
updateMap
:: Ord k
=> k
-- ^ The key
-> a
-- ^ The default initial value
-> (a -> a)
-- ^ The modification function
-> M.Map k a
-- ^ The map
-> M.Map k a
updateMap k def f m = let
x = M.findWithDefault def k m
in M.insert k (f x) m
-- | Selects a section of a list
slice
:: Int
-- ^ The index to start at
-> Int
-- ^ The number of elements to take
-> [a]
-- ^ The list to take a subset of
-> [a]
slice offset len = take len . drop offset

View File

@@ -19,15 +19,29 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
-} -}
{-# LANGUAGE LambdaCase #-}
module ActionsSpec (spec) where module ActionsSpec (spec) where
import Control.Monad (replicateM) import Control.Monad (replicateM)
import qualified Data.Map as M
import Data.Maybe (fromJust)
import Lens.Micro ((^.), (&), (.~), (?~), (%~)) import Lens.Micro ((^.), (&), (.~), (?~), (%~))
import System.Random (randomRIO) import System.Random (randomRIO)
import Test.Hspec (Spec, context, describe, it, shouldBe, shouldNotBe) import Test.Hspec
( Spec
, context
, describe
, it
, runIO
, shouldBe
, shouldNotBe
, shouldSatisfy
)
import Mtlstats.Actions import Mtlstats.Actions
import Mtlstats.Types import Mtlstats.Types
import Mtlstats.Util
spec :: Spec spec :: Spec
spec = describe "Mtlstats.Actions" $ do spec = describe "Mtlstats.Actions" $ do
@@ -41,7 +55,14 @@ spec = describe "Mtlstats.Actions" $ do
validateGameDateSpec validateGameDateSpec
createPlayerSpec createPlayerSpec
addPlayerSpec addPlayerSpec
recordGoalAssistsSpec
awardGoalSpec awardGoalSpec
awardAssistSpec
resetGoalDataSpec
assignPMinsSpec
backHomeSpec
scrollUpSpec
scrollDownSpec
startNewSeasonSpec :: Spec startNewSeasonSpec :: Spec
startNewSeasonSpec = describe "startNewSeason" $ do startNewSeasonSpec = describe "startNewSeason" $ do
@@ -193,9 +214,11 @@ updateGameStatsSpec = describe "updateGameStats" $ do
let let
baseStats = newGameStats baseStats = newGameStats
& gmsWins .~ 1 & gmsWins .~ 1
& gmsLosses .~ 1 & gmsLosses .~ 1
& gmsOvertime .~ 1 & gmsOvertime .~ 1
& gmsGoalsFor .~ 1
& gmsGoalsAgainst .~ 1
s t h a o = newProgState s t h a o = newProgState
& progMode.gameStateL & progMode.gameStateL
@@ -207,75 +230,79 @@ updateGameStatsSpec = describe "updateGameStats" $ do
%~ (dbHomeGameStats .~ baseStats) %~ (dbHomeGameStats .~ baseStats)
. (dbAwayGameStats .~ baseStats) . (dbAwayGameStats .~ baseStats)
db hw hl ho aw al ao = newDatabase db hw hl ho hf ha aw al ao af aa = newDatabase
& dbHomeGameStats & dbHomeGameStats
%~ (gmsWins .~ hw) %~ (gmsWins .~ hw)
. (gmsLosses .~ hl) . (gmsLosses .~ hl)
. (gmsOvertime .~ ho) . (gmsOvertime .~ ho)
. (gmsGoalsFor .~ hf)
. (gmsGoalsAgainst .~ ha)
& dbAwayGameStats & dbAwayGameStats
%~ (gmsWins .~ aw) %~ (gmsWins .~ aw)
. (gmsLosses .~ al) . (gmsLosses .~ al)
. (gmsOvertime .~ ao) . (gmsOvertime .~ ao)
. (gmsGoalsFor .~ af)
. (gmsGoalsAgainst .~ aa)
context "home win" $ context "home win" $
it "should record a home win" $ let it "should record a home win" $ let
s' = s (Just HomeGame) (Just 2) (Just 1) (Just False) s' = s (Just HomeGame) (Just 2) (Just 1) (Just False)
db' = updateGameStats s' ^. database db' = updateGameStats s' ^. database
in db' `shouldBe` db 2 1 1 1 1 1 in db' `shouldBe` db 2 1 1 3 2 1 1 1 1 1
context "home loss" $ context "home loss" $
it "should record a home loss" $ let it "should record a home loss" $ let
s' = s (Just HomeGame) (Just 1) (Just 2) (Just False) s' = s (Just HomeGame) (Just 1) (Just 2) (Just False)
db' = updateGameStats s' ^. database db' = updateGameStats s' ^. database
in db' `shouldBe` db 1 2 1 1 1 1 in db' `shouldBe` db 1 2 1 2 3 1 1 1 1 1
context "home overtime loss" $ context "home overtime loss" $
it "should record a home overtime" $ let it "should record a home overtime" $ let
s' = s (Just HomeGame) (Just 1) (Just 2) (Just True) s' = s (Just HomeGame) (Just 1) (Just 2) (Just True)
db' = updateGameStats s' ^. database db' = updateGameStats s' ^. database
in db' `shouldBe` db 1 1 2 1 1 1 in db' `shouldBe` db 1 1 2 2 3 1 1 1 1 1
context "away win" $ context "away win" $
it "should record an away win" $ let it "should record an away win" $ let
s' = s (Just AwayGame) (Just 1) (Just 2) (Just False) s' = s (Just AwayGame) (Just 1) (Just 2) (Just False)
db' = updateGameStats s' ^. database db' = updateGameStats s' ^. database
in db' `shouldBe` db 1 1 1 2 1 1 in db' `shouldBe` db 1 1 1 1 1 2 1 1 3 2
context "away loss" $ context "away loss" $
it "should record an away loss" $ let it "should record an away loss" $ let
s' = s (Just AwayGame) (Just 2) (Just 1) (Just False) s' = s (Just AwayGame) (Just 2) (Just 1) (Just False)
db' = updateGameStats s' ^. database db' = updateGameStats s' ^. database
in db' `shouldBe` db 1 1 1 1 2 1 in db' `shouldBe` db 1 1 1 1 1 1 2 1 2 3
context "away overtime loss" $ context "away overtime loss" $
it "should record an away overtime" $ let it "should record an away overtime" $ let
s' = s (Just AwayGame) (Just 2) (Just 1) (Just True) s' = s (Just AwayGame) (Just 2) (Just 1) (Just True)
db' = updateGameStats s' ^. database db' = updateGameStats s' ^. database
in db' `shouldBe` db 1 1 1 1 1 2 in db' `shouldBe` db 1 1 1 1 1 1 1 2 2 3
context "missing game type" $ context "missing game type" $
it "should not change anything" $ let it "should not change anything" $ let
s' = s Nothing (Just 1) (Just 2) (Just True) s' = s Nothing (Just 1) (Just 2) (Just True)
db' = updateGameStats s' ^. database db' = updateGameStats s' ^. database
in db' `shouldBe` db 1 1 1 1 1 1 in db' `shouldBe` db 1 1 1 1 1 1 1 1 1 1
context "missing home score" $ context "missing home score" $
it "should not change anything" $ let it "should not change anything" $ let
s' = s (Just HomeGame) Nothing (Just 1) (Just True) s' = s (Just HomeGame) Nothing (Just 1) (Just True)
db' = updateGameStats s' ^. database db' = updateGameStats s' ^. database
in db' `shouldBe` db 1 1 1 1 1 1 in db' `shouldBe` db 1 1 1 1 1 1 1 1 1 1
context "missing away score" $ context "missing away score" $
it "should not change anything" $ let it "should not change anything" $ let
s' = s (Just HomeGame) (Just 1) Nothing (Just True) s' = s (Just HomeGame) (Just 1) Nothing (Just True)
db' = updateGameStats s' ^. database db' = updateGameStats s' ^. database
in db' `shouldBe` db 1 1 1 1 1 1 in db' `shouldBe` db 1 1 1 1 1 1 1 1 1 1
context "missing overtime flag" $ context "missing overtime flag" $
it "should not change anything" $ let it "should not change anything" $ let
s' = s (Just HomeGame) (Just 1) (Just 2) Nothing s' = s (Just HomeGame) (Just 1) (Just 2) Nothing
db' = updateGameStats s' ^. database db' = updateGameStats s' ^. database
in db' `shouldBe` db 1 1 1 1 1 1 in db' `shouldBe` db 1 1 1 1 1 1 1 1 1 1
validateGameDateSpec :: Spec validateGameDateSpec :: Spec
validateGameDateSpec = describe "validateGameDate" $ do validateGameDateSpec = describe "validateGameDate" $ do
@@ -334,7 +361,7 @@ addPlayerSpec = describe "addPlayer" $ do
p1 = newPlayer 1 "Joe" "centre" p1 = newPlayer 1 "Joe" "centre"
p2 = newPlayer 2 "Bob" "defense" p2 = newPlayer 2 "Bob" "defense"
db = newDatabase db = newDatabase
& dbPlayers .~ [p2] & dbPlayers .~ [p1]
s pm = newProgState s pm = newProgState
& progMode .~ pm & progMode .~ pm
& database .~ db & database .~ db
@@ -342,15 +369,76 @@ addPlayerSpec = describe "addPlayer" $ do
context "data available" $ context "data available" $
it "should create the player" $ let it "should create the player" $ let
s' = addPlayer $ s $ CreatePlayer $ newCreatePlayerState s' = addPlayer $ s $ CreatePlayer $ newCreatePlayerState
& cpsNumber ?~ 1 & cpsNumber ?~ 2
& cpsName .~ "Joe" & cpsName .~ "Bob"
& cpsPosition .~ "centre" & cpsPosition .~ "defense"
in s'^.database.dbPlayers `shouldBe` [p1, p2] in s'^.database.dbPlayers `shouldBe` [p1, p2]
context "data unavailable" $ context "data unavailable" $
it "should not create the player" $ let it "should not create the player" $ let
s' = addPlayer $ s MainMenu s' = addPlayer $ s MainMenu
in s'^.database.dbPlayers `shouldBe` [p2] in s'^.database.dbPlayers `shouldBe` [p1]
recordGoalAssistsSpec :: Spec
recordGoalAssistsSpec = describe "recordGoalAssists" $ do
let
joe = newPlayer 1 "Joe" "centre"
bob = newPlayer 2 "Bob" "defense"
steve = newPlayer 3 "Steve" "forward"
dave = newPlayer 4 "Dave" "somewhere"
frank = newPlayer 5 "Frank" "elsewhere"
ps
= newProgState
& database.dbPlayers .~ [joe, bob, steve, dave, frank]
& progMode.gameStateL
%~ (goalBy ?~ 0)
. (assistsBy .~ [1, 2])
. (confirmGoalDataFlag .~ True)
& recordGoalAssists
mapM_
(\(name, n, goals, assists) -> context name $ do
let
player = (ps^.database.dbPlayers) !! n
stats = M.findWithDefault newPlayerStats n $
ps^.progMode.gameStateL.gamePlayerStats
it ("should set the year-to-date goals to " ++ show goals) $
player^.pYtd.psGoals `shouldBe` goals
it ("should set the lifetime goals to " ++ show goals) $
player^.pLifetime.psGoals `shouldBe` goals
it ("should set the game goals to " ++ show goals) $
stats^.psAssists `shouldBe` assists
it ("should set the year-to-date assists to " ++ show assists) $
player^.pYtd.psAssists `shouldBe` assists
it ("should set the lifetime assists to " ++ show assists) $
player^.pLifetime.psAssists `shouldBe` assists
it ("should set the game assists to " ++ show assists) $
stats^.psAssists `shouldBe` assists)
-- name, index, goals, assists
[ ( "Joe", 0, 1, 0 )
, ( "Bob", 1, 0, 1 )
, ( "Steve", 2, 0, 1 )
, ( "Dave", 3, 0, 0 )
]
it "should clear the goalBy value" $
ps^.progMode.gameStateL.goalBy `shouldBe` Nothing
it "should clear the assistsBy list" $
ps^.progMode.gameStateL.assistsBy `shouldBe` []
it "should increment the pointsAccounted counter" $
ps^.progMode.gameStateL.pointsAccounted `shouldBe` 1
it "should clear the confirmGoalDataFlag" $
ps^.progMode.gameStateL.confirmGoalDataFlag `shouldBe` False
awardGoalSpec :: Spec awardGoalSpec :: Spec
awardGoalSpec = describe "awardGoal" $ do awardGoalSpec = describe "awardGoal" $ do
@@ -366,31 +454,34 @@ awardGoalSpec = describe "awardGoal" $ do
db db
= newDatabase = newDatabase
& dbPlayers .~ [joe, bob] & dbPlayers .~ [joe, bob]
joeStats
= newPlayerStats
& psGoals .~ 1
ps ps
= newProgState = newProgState
& progMode.gameStateL.gamePlayerStats .~ M.singleton 0 joeStats
& database .~ db & database .~ db
context "Joe" $ do mapM_
let (\(pName, pid, ytd, lt, game) ->
ps' = awardGoal 0 ps context pName $ do
player = head $ ps'^.database.dbPlayers let
ps' = awardGoal pid ps
player = (ps'^.database.dbPlayers) !! pid
gStats = (ps'^.progMode.gameStateL.gamePlayerStats) M.! pid
it "should increment Joe's year-to-date goals" $ it ("should increment " ++ pName ++ "'s year-to-date goals") $
player^.pYtd.psGoals `shouldBe` 2 player^.pYtd.psGoals `shouldBe` ytd
it "should increment Joe's lifetime goals" $ it ("should increment " ++ pName ++ "'s lifetime goals") $
player^.pLifetime.psGoals `shouldBe` 3 player^.pLifetime.psGoals `shouldBe` lt
context "Bob" $ do it ("should increment " ++ pName ++ "'s game goals") $
let gStats^.psGoals `shouldBe` game)
ps' = awardGoal 1 ps -- player name, player id, ytd goals, lifetime goals, game goals
player = last $ ps'^.database.dbPlayers [ ( "Joe", 0, 2, 3, 2 )
, ( "Bob", 1, 4, 5, 1 )
it "should increment Bob's year-to-data goals" $ ]
player^.pYtd.psGoals `shouldBe` 4
it "should increment Bob's lifetime goals" $
player^.pLifetime.psGoals `shouldBe` 5
context "invalid index" $ let context "invalid index" $ let
ps' = awardGoal 2 ps ps' = awardGoal 2 ps
@@ -402,6 +493,131 @@ awardGoalSpec = describe "awardGoal" $ do
in it "should not change the database" $ in it "should not change the database" $
ps'^.database `shouldBe` db ps'^.database `shouldBe` db
awardAssistSpec :: Spec
awardAssistSpec = describe "awardAssist" $ do
let
joe
= newPlayer 1 "Joe" "centre"
& pYtd.psAssists .~ 1
& pLifetime.psAssists .~ 2
bob
= newPlayer 2 "Bob" "defense"
& pYtd.psAssists .~ 3
& pLifetime.psAssists .~ 4
joeStats
= newPlayerStats
& psAssists .~ 1
ps
= newProgState
& progMode.gameStateL.gamePlayerStats .~ M.singleton 0 joeStats
& database.dbPlayers .~ [joe, bob]
mapM_
(\(pName, pid, ytd, lt, game) ->
context pName $ do
let
ps' = awardAssist pid ps
player = (ps'^.database.dbPlayers) !! pid
gStats = (ps'^.progMode.gameStateL.gamePlayerStats) M.! pid
it ("should increment " ++ pName ++ "'s year-to-date assists") $
player^.pYtd.psAssists `shouldBe` ytd
it ("should increment " ++ pName ++ "'s lifetime assists") $
player^.pLifetime.psAssists `shouldBe` lt
it ("should increment " ++ pName ++ "'s game assists") $
gStats^.psAssists `shouldBe` game)
-- player name, player id, ytd assists, lifetime assists, game assists
[ ( "Joe", 0, 2, 3, 2 )
, ( "Bob", 1, 4, 5, 1 )
]
context "invalid index" $ let
ps' = awardAssist (-1) ps
in it "should not change anything" $
ps'^.database.dbPlayers `shouldBe` ps^.database.dbPlayers
resetGoalDataSpec :: Spec
resetGoalDataSpec = describe "resetGoalData" $ do
players <- runIO $ replicateM 5 makePlayer
let
gs
= newGameState
& goalBy ?~ 1
& assistsBy .~ [2, 3]
& confirmGoalDataFlag .~ True
ps
= newProgState
& database.dbPlayers .~ players
& progMode.gameStateL .~ gs
& resetGoalData
it "should clear the goalBy value" $
ps^.progMode.gameStateL.goalBy `shouldBe` Nothing
it "should clear the assists by list" $
ps^.progMode.gameStateL.assistsBy `shouldBe` []
it "should clear confirmGoalDataFlag" $
ps^.progMode.gameStateL.confirmGoalDataFlag `shouldBe` False
assignPMinsSpec :: Spec
assignPMinsSpec = describe "assignPMins" $ let
bob = newPlayer 2 "Bob" "centre"
& pYtd.psPMin .~ 3
& pLifetime.psPMin .~ 4
joe = newPlayer 3 "Joe" "defense"
& pYtd.psPMin .~ 5
& pLifetime.psPMin .~ 6
ps pid = newProgState
& database.dbPlayers .~ [bob, joe]
& progMode.gameStateL
%~ (gamePlayerStats .~ M.fromList [(0, newPlayerStats & psPMin .~ 2)])
. (selectedPlayer .~ pid)
in mapM_
(\(pid, bobLt, bobYtd, bobGame, joeLt, joeYtd, joeGame) ->
context ("selectedPlayer = " ++ show pid) $ do
let ps' = assignPMins 2 $ ps pid
mapM_
(\(name, pid', lt, ytd, game) -> context name $ do
let
player = fromJust $ nth pid' $ ps'^.database.dbPlayers
gStats = ps'^.progMode.gameStateL.gamePlayerStats
pStats = M.findWithDefault newPlayerStats pid' gStats
context "lifetime penalty minutes" $
it ("should be " ++ show lt) $
player^.pLifetime.psPMin `shouldBe` lt
context "year-to-date penalty minutes" $
it ("should be " ++ show ytd) $
player^.pYtd.psPMin `shouldBe` ytd
context "game penalty minutes" $
it ("should be " ++ show game) $
pStats^.psPMin `shouldBe` game)
-- name, index, lifetime, ytd, game
[ ( "Bob", 0, bobLt, bobYtd, bobGame )
, ( "Joe", 1, joeLt, joeYtd, joeGame )
]
it "should set selectedPlayer to Nothing" $
ps'^.progMode.gameStateL.selectedPlayer `shouldBe` Nothing)
-- index, bob lt, bob ytd, bob game, joe lt, joe ytd, joe game
[ ( Just 0, 6, 5, 4, 6, 5, 0 )
, ( Just 1, 4, 3, 2, 8, 7, 2 )
, ( Just 2, 4, 3, 2, 6, 5, 0 )
, ( Nothing, 4, 3, 2, 6, 5, 0 )
]
makePlayer :: IO Player makePlayer :: IO Player
makePlayer = Player makePlayer = Player
<$> makeNum <$> makeNum
@@ -438,3 +654,49 @@ makeNum = randomRIO (1, 10)
makeName :: IO String makeName :: IO String
makeName = replicateM 10 $ randomRIO ('A', 'Z') makeName = replicateM 10 $ randomRIO ('A', 'Z')
backHomeSpec :: Spec
backHomeSpec = describe "backHome" $ do
let
input = newProgState
& progMode.gameStateL .~ newGameState
& inputBuffer .~ "foo"
& scrollOffset .~ 123
result = backHome input
it "should set the program mode back to MainMenu" $
result^.progMode `shouldSatisfy` \case
MainMenu -> True
_ -> False
it "should clear the input buffer" $
result^.inputBuffer `shouldBe` ""
it "should reset the scroll offset" $
result^.scrollOffset `shouldBe` 0
scrollUpSpec :: Spec
scrollUpSpec = describe "scrollUp" $ do
context "scrolled down" $
it "should decrease the scroll offset by one" $ let
ps = newProgState & scrollOffset .~ 10
ps' = scrollUp ps
in ps'^.scrollOffset `shouldBe` 9
context "at top" $
it "should keep the scroll offset at zero" $ let
ps = scrollUp newProgState
in ps^.scrollOffset `shouldBe` 0
context "above top" $
it "should return the scroll offset to zero" $ let
ps = newProgState & scrollOffset .~ (-10)
ps' = scrollUp ps
in ps'^.scrollOffset `shouldBe` 0
scrollDownSpec = describe "scrollDown" $
it "should increase the scroll offset" $ let
ps = newProgState & scrollOffset .~ 10
ps' = scrollDown ps
in ps'^.scrollOffset `shouldBe` 11

View File

@@ -28,8 +28,9 @@ import Mtlstats.Report
import Mtlstats.Types import Mtlstats.Types
spec :: Spec spec :: Spec
spec = describe "Mtlstats.Report" spec = describe "Mtlstats.Report" $ do
gameDateSpec gameDateSpec
playerNameColWidthSpec
gameDateSpec :: Spec gameDateSpec :: Spec
gameDateSpec = describe "gameDate" $ do gameDateSpec = describe "gameDate" $ do
@@ -45,3 +46,20 @@ gameDateSpec = describe "gameDate" $ do
context "invalid date" $ context "invalid date" $
it "should return an empty string" $ it "should return an empty string" $
gameDate newGameState `shouldBe` "" gameDate newGameState `shouldBe` ""
playerNameColWidthSpec :: Spec
playerNameColWidthSpec = describe "playerNameColWidth" $ do
let
short1 = newPlayer 1 "short" "foo"
short2 = newPlayer 2 "shorty" "bar"
long = newPlayer 3 "123456789012345" "baz"
mapM_
(\(label, players, expected) -> context label $
it ("should be " ++ show expected) $
playerNameColWidth players `shouldBe` expected)
-- label, players, expected
[ ( "empty list", [], 10 )
, ( "short names", [short1, short2], 10 )
, ( "long name", [short1, long], 16 )
]

View File

@@ -54,9 +54,13 @@ spec = describe "Mtlstats.Types" $ do
gmsGamesSpec gmsGamesSpec
gmsPointsSpec gmsPointsSpec
addGameStatsSpec addGameStatsSpec
pPointsSpec
playerSearchSpec playerSearchSpec
playerSearchExactSpec playerSearchExactSpec
modifyPlayerSpec
playerSummarySpec
playerIsActiveSpec
psPointsSpec
addPlayerStatsSpec
Menu.spec Menu.spec
playerSpec :: Spec playerSpec :: Spec
@@ -255,16 +259,20 @@ goalieStatsJSON n = Object $ HM.fromList
gameStats :: Int -> GameStats gameStats :: Int -> GameStats
gameStats n = GameStats gameStats n = GameStats
{ _gmsWins = n { _gmsWins = n
, _gmsLosses = n + 1 , _gmsLosses = n + 1
, _gmsOvertime = n + 2 , _gmsOvertime = n + 2
, _gmsGoalsFor = n + 3
, _gmsGoalsAgainst = n + 4
} }
gameStatsJSON :: Int -> Value gameStatsJSON :: Int -> Value
gameStatsJSON n = Object $ HM.fromList gameStatsJSON n = Object $ HM.fromList
[ ( "wins", toJSON n ) [ ( "wins", toJSON n )
, ( "losses", toJSON $ n + 1 ) , ( "losses", toJSON $ n + 1 )
, ( "overtime", toJSON $ n + 2 ) , ( "overtime", toJSON $ n + 2 )
, ( "goals_for", toJSON $ n + 3 )
, ( "goals_against", toJSON $ n + 4 )
] ]
db :: Database db :: Database
@@ -456,11 +464,11 @@ gmsGamesSpec = describe "gmsGames" $ mapM_
gmsPointsSpec :: Spec gmsPointsSpec :: Spec
gmsPointsSpec = describe "gmsPoints" $ mapM_ gmsPointsSpec = describe "gmsPoints" $ mapM_
(\(w, l, ot, expected) -> let (\(w, l, ot, expected) -> let
gs = GameStats gs
{ _gmsWins = w = newGameStats
, _gmsLosses = l & gmsWins .~ w
, _gmsOvertime = ot & gmsLosses .~ l
} & gmsOvertime .~ ot
in context (show gs) $ in context (show gs) $
it ("should be " ++ show expected) $ it ("should be " ++ show expected) $
gmsPoints gs `shouldBe` expected) gmsPoints gs `shouldBe` expected)
@@ -478,43 +486,31 @@ addGameStatsSpec = describe "addGameStats" $
it "should add the values" $ let it "should add the values" $ let
s1 = GameStats s1 = GameStats
{ _gmsWins = 1 { _gmsWins = 1
, _gmsLosses = 3 , _gmsLosses = 2
, _gmsOvertime = 2 , _gmsOvertime = 3
, _gmsGoalsFor = 4
, _gmsGoalsAgainst = 5
} }
s2 = GameStats s2 = GameStats
{ _gmsWins = 4 { _gmsWins = 6
, _gmsLosses = 6 , _gmsLosses = 7
, _gmsOvertime = 5 , _gmsOvertime = 8
, _gmsGoalsFor = 9
, _gmsGoalsAgainst = 10
} }
expected = GameStats expected = GameStats
{ _gmsWins = 5 { _gmsWins = 7
, _gmsLosses = 9 , _gmsLosses = 9
, _gmsOvertime = 7 , _gmsOvertime = 11
, _gmsGoalsFor = 13
, _gmsGoalsAgainst = 15
} }
in addGameStats s1 s2 `shouldBe` expected in addGameStats s1 s2 `shouldBe` expected
pPointsSpec :: Spec
pPointsSpec = describe "pPoints" $ mapM_
(\(goals, assists, points) -> let
desc = "goals: " ++ show goals ++
", assists: " ++ show assists
stats = newPlayerStats &
psGoals .~ goals &
psAssists .~ assists
in context desc $
it ("should be " ++ show points) $
pPoints stats `shouldBe` points)
-- goals, assists, points
[ ( 0, 0, 0 )
, ( 1, 0, 1 )
, ( 0, 1, 1 )
, ( 2, 3, 5 )
]
playerSearchSpec :: Spec playerSearchSpec :: Spec
playerSearchSpec = describe "playerSearch" $ mapM_ playerSearchSpec = describe "playerSearch" $ mapM_
(\(sStr, expected) -> context sStr $ (\(sStr, expected) -> context sStr $
@@ -542,6 +538,101 @@ playerSearchExactSpec = describe "playerSearchExact" $ mapM_
, ( "", Nothing ) , ( "", Nothing )
] ]
modifyPlayerSpec :: Spec
modifyPlayerSpec = describe "modifyPlayer" $ mapM_
(\(pName, j, b, s) -> let
modifier = pLifetime.psGoals .~ 1
players = modifyPlayer modifier pName [joe, bob, steve]
in context ("modify " ++ pName) $ do
context "Joe's lifetime goals" $
it ("should be " ++ show j) $
head players ^. pLifetime.psGoals `shouldBe` j
context "Bob's lifetime goals" $
it ("should be " ++ show b) $
(players !! 1) ^. pLifetime.psGoals `shouldBe` b
context "Steve's lifetime goals" $
it ("should be " ++ show s) $
last players ^. pLifetime.psGoals `shouldBe` s)
-- player name, Joe's goals, Bob's goals, Steve's goals
[ ( "Joe", 1, 0, 0 )
, ( "Bob", 0, 1, 0 )
, ( "Steve", 0, 0, 1 )
, ( "Sam", 0, 0, 0 )
]
playerSummarySpec :: Spec
playerSummarySpec = describe "playerSummary" $
it "should be \"Joe (2) center\"" $
playerSummary joe `shouldBe` "Joe (2) center"
playerIsActiveSpec :: Spec
playerIsActiveSpec = describe "playerIsActive" $ do
let
pState = newPlayerStats
& psGoals .~ 10
& psAssists .~ 11
& psPMin .~ 12
player = newPlayer 1 "Joe" "centre" & pLifetime .~ pState
mapM_
(\(label, player', expected) -> context label $
it ("should be " ++ show expected) $
playerIsActive player' `shouldBe` expected)
-- label, player, expected
[ ( "not active", player, False )
, ( "has goal", player & pYtd.psGoals .~ 1, True )
, ( "has assist", player & pYtd.psAssists .~ 1, True )
, ( "has penalty minute", player & pYtd.psPMin .~ 1, True )
]
psPointsSpec :: Spec
psPointsSpec = describe "psPoints" $ mapM_
(\(goals, assists, points) -> let
desc = "goals: " ++ show goals ++
", assists: " ++ show assists
stats = newPlayerStats &
psGoals .~ goals &
psAssists .~ assists
in context desc $
it ("should be " ++ show points) $
psPoints stats `shouldBe` points)
-- goals, assists, points
[ ( 0, 0, 0 )
, ( 1, 0, 1 )
, ( 0, 1, 1 )
, ( 2, 3, 5 )
]
addPlayerStatsSpec :: Spec
addPlayerStatsSpec = describe "addPlayerStats" $ do
let
s1
= newPlayerStats
& psGoals .~ 1
& psAssists .~ 2
& psPMin .~ 3
s2
= newPlayerStats
& psGoals .~ 4
& psAssists .~ 5
& psPMin .~ 6
s3 = addPlayerStats s1 s2
describe "psGoals" $
it "should be 5" $
s3^.psGoals `shouldBe` 5
describe "psAssists" $
it "should be 7" $
s3^.psAssists `shouldBe` 7
describe "psPMin" $
it "should be 9" $
s3^.psPMin `shouldBe` 9
joe :: Player joe :: Player
joe = newPlayer 2 "Joe" "center" joe = newPlayer 2 "Joe" "center"

View File

@@ -21,13 +21,17 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
module UtilSpec (spec) where module UtilSpec (spec) where
import qualified Data.Map as M
import Test.Hspec (Spec, context, describe, it, shouldBe) import Test.Hspec (Spec, context, describe, it, shouldBe)
import Mtlstats.Util import Mtlstats.Util
spec :: Spec spec :: Spec
spec = describe "Mtlstats.Util" spec = describe "Mtlstats.Util" $ do
nthSpec nthSpec
modifyNthSpec
updateMapSpec
sliceSpec
nthSpec :: Spec nthSpec :: Spec
nthSpec = describe "nth" $ mapM_ nthSpec = describe "nth" $ mapM_
@@ -42,3 +46,49 @@ nthSpec = describe "nth" $ mapM_
, ( 3, Nothing ) , ( 3, Nothing )
, ( -1, Nothing ) , ( -1, Nothing )
] ]
modifyNthSpec :: Spec
modifyNthSpec = describe "modifyNth" $ do
context "in bounds" $
it "should modify the value" $
modifyNth 1 succ [1, 2, 3] `shouldBe` [1, 3, 3]
context "out of bounds" $
it "should not modify the value" $
modifyNth 3 succ [1, 2, 3] `shouldBe` [1, 2, 3]
context "negative index" $
it "should not modify the value" $
modifyNth (-1) succ [1, 2, 3] `shouldBe` [1, 2, 3]
updateMapSpec :: Spec
updateMapSpec = describe "updateMap" $ do
let
input = M.fromList [(1, 2), (3, 5)]
context "key found" $ let
expected = M.fromList [(1, 3), (3, 5)]
in it "should update the value" $
updateMap 1 10 succ input `shouldBe` expected
context "key not found" $ let
expected = M.fromList [(1, 2), (3, 5), (10, 11)]
in it "should create a new value and update the default" $
updateMap 10 10 succ input `shouldBe` expected
sliceSpec :: Spec
sliceSpec = describe "slice" $ do
let list = [2, 4, 6, 8]
context "sublist" $
it "should return the sublist" $
slice 1 2 list `shouldBe` [4, 6]
context "too large" $
it "should return as much of the list as possible" $
slice 1 100 list `shouldBe` [4, 6, 8]
context "negative offset" $
it "should return the correct number of elements from the beginning" $
slice (-10) 2 list `shouldBe` [2, 4]