73 Commits
0.1.0 ... 0.3.0

Author SHA1 Message Date
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
Jonathan Lamothe
5332dc0d7f version 0.2.0 2019-09-19 23:46:38 -04:00
Jonathan Lamothe
2ac9aad199 updated change log 2019-09-19 13:02:46 -04:00
Jonathan Lamothe
49b909e4b1 Merge pull request #18 from mtlstats/db
load/save database
2019-09-19 12:48:36 -04:00
Jonathan Lamothe
23a33fc27a save database on exit 2019-09-19 07:34:19 -04:00
Jonathan Lamothe
d58293bef5 load database on start 2019-09-19 07:34:19 -04:00
Jonathan Lamothe
4985d2694a Merge pull request #17 from mtlstats/goal-points
Goal points
2019-09-19 06:42:06 -04:00
Jonathan Lamothe
e3388c45c7 limit number of player shortcuts displayed 2019-09-19 06:34:03 -04:00
Jonathan Lamothe
f7e6ac9437 clear input buffer after player selection with function key 2019-09-19 06:25:38 -04:00
Jonathan Lamothe
a66be1a45e prompt user for players who've scored goals 2019-09-19 06:21:04 -04:00
Jonathan Lamothe
e80b7ec48c implemented recordGoalPrompt 2019-09-19 06:21:04 -04:00
Jonathan Lamothe
b125e72034 implemented awardGoal 2019-09-19 06:21:04 -04:00
Jonathan Lamothe
156c2baaba implemented selectPlayerPrompt 2019-09-19 06:21:04 -04:00
Jonathan Lamothe
11a66cfd33 allow player creation callbacks to be impure 2019-09-19 04:01:28 -04:00
Jonathan Lamothe
8277f8bac7 implemented playerSearchExact 2019-09-19 03:11:48 -04:00
Jonathan Lamothe
d4cfbcb968 changed promptFunctionKey to promptSpecialKey 2019-09-19 02:45:50 -04:00
Jonathan Lamothe
3d705c4e6d implemented nth 2019-09-18 02:45:20 -04:00
Jonathan Lamothe
ed9e437a1a implemented playerSearch 2019-09-18 02:45:20 -04:00
Jonathan Lamothe
2ff8cff1c8 implemented unaccountedPoints helper function 2019-09-14 00:42:04 -04:00
Jonathan Lamothe
926a125692 added pointsAccounted field to GameState 2019-09-14 00:41:38 -04:00
Jonathan Lamothe
1a25c0dc92 made callbacks pure 2019-09-14 00:03:26 -04:00
Jonathan Lamothe
6ceb5415c5 use player creation callbacks 2019-09-13 23:54:36 -04:00
Jonathan Lamothe
6dd9350189 added callbacks to CreatePlayerState 2019-09-13 02:26:03 -04:00
Jonathan Lamothe
db0084f991 Merge pull request #16 from mtlstats/refactor
removed cpsConfirmed from CreatePlayerStatus
2019-09-10 16:22:40 -04:00
Jonathan Lamothe
06a762cfdc removed cpsConfirmed from CreatePlayerStatus 2019-09-10 16:07:46 -04:00
Jonathan Lamothe
fde8965b06 Merge pull request #15 from mtlstats/create-player
Create player
2019-09-09 23:42:09 -04:00
Jonathan Lamothe
375e87a49e implemented player confirmation/addition 2019-09-09 23:35:28 -04:00
Jonathan Lamothe
0ee0451496 prompt for player's position 2019-09-09 22:58:04 -04:00
Jonathan Lamothe
57ac90038a prompt for player name 2019-09-09 22:58:04 -04:00
Jonathan Lamothe
754b3dd25c prompt for player number 2019-09-09 22:58:04 -04:00
Jonathan Lamothe
154c3979a5 implemented createPlayerStateL 2019-09-09 11:43:37 -04:00
Jonathan Lamothe
d5d08aa0f7 added create player option to main menu 2019-09-09 10:51:32 -04:00
Jonathan Lamothe
6b73e367e4 implemented CreatePlayerState 2019-09-08 12:06:38 -04:00
Jonathan Lamothe
be5d10b6fd moved ProgMode 2019-09-07 11:33:03 -04:00
Jonathan Lamothe
4891605089 updated change log 2019-09-07 09:38:05 -04:00
Jonathan Lamothe
6cb348a4a8 Merge pull request #14 from mtlstats/verify-input
Verify input
2019-09-07 09:15:29 -04:00
Jonathan Lamothe
9c0ebb42d1 renamed date to gameDate 2019-09-07 09:06:16 -04:00
Jonathan Lamothe
dc2f632563 prompt for confirmation of game input 2019-09-07 00:27:18 -04:00
Jonathan Lamothe
27867ba69d implemented Mtlstats.Report.date 2019-09-07 00:26:15 -04:00
Jonathan Lamothe
e0dd80079d implemented ynHandler 2019-09-06 23:25:13 -04:00
Jonathan Lamothe
1e7c4d6c19 added dataVerified field to GameState 2019-09-06 11:21:46 -04:00
Jonathan Lamothe
00c96e763d Merge pull request #13 from mtlstats/ot-fix
overtime losses don't count towards the loss column
2019-09-06 10:21:55 -04:00
Jonathan Lamothe
a9ce9a54d7 overtime losses don't count towards the loss column 2019-09-06 10:13:48 -04:00
18 changed files with 1489 additions and 281 deletions

View File

@@ -1,3 +1,14 @@
# Changelog for mtlstats
## Unreleased changes
## v0.3.0
- Record goals and assists
- Track goals for and goals against
## v0.2.0
- Overtime losses don't count in the loss column
- Confirm game data with user before updating stats
- Implemented player creation
- Goal points are now assigned to players
- Loading/saving of database

View File

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

View File

@@ -19,15 +19,23 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
-}
{-# LANGUAGE ScopedTypeVariables #-}
module Mtlstats (initState, mainLoop) where
import Control.Exception (IOException, catch)
import Control.Monad (void)
import Control.Monad.Extra (whenM)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.State (get, gets)
import Data.Maybe (fromJust)
import Data.Aeson (decodeFileStrict)
import Data.Maybe (fromJust, fromMaybe)
import Lens.Micro ((&), (.~))
import System.EasyFile (getAppUserDataDirectory, (</>))
import qualified UI.NCurses as C
import Mtlstats.Config
import Mtlstats.Control
import Mtlstats.Types
@@ -36,7 +44,15 @@ initState :: C.Curses ProgState
initState = do
C.setEcho False
void $ C.setCursorMode C.CursorInvisible
return newProgState
db <- liftIO $ do
dir <- getAppUserDataDirectory appName
let dbFile = dir </> dbFname
fromMaybe newDatabase <$> catch
(decodeFileStrict dbFile)
(\(_ :: IOException) -> return Nothing)
return
$ newProgState
& database .~ db
-- | Main program loop
mainLoop :: Action ()

View File

@@ -30,8 +30,16 @@ module Mtlstats.Actions
, overtimeCheck
, updateGameStats
, validateGameDate
, createPlayer
, addPlayer
, recordGoalAssists
, awardGoal
, awardAssist
, resetGoalData
) where
import Control.Monad.Trans.State (modify)
import qualified Data.Map as M
import Data.Maybe (fromMaybe)
import Data.Time.Calendar (fromGregorianValid)
import Lens.Micro (over, (^.), (&), (.~), (?~), (%~), (+~))
@@ -78,26 +86,37 @@ overtimeCheck s
-- | Adjusts the game stats based on the results of the current game
updateGameStats :: ProgState -> ProgState
updateGameStats s = fromMaybe s $ do
gType <- s^.progMode.gameStateL.gameType
won <- gameWon $ s^.progMode.gameStateL
lost <- gameLost $ s^.progMode.gameStateL
ot <- s^.progMode.gameStateL.overtimeFlag
let gs = s^.progMode.gameStateL
gType <- gs^.gameType
won <- gameWon gs
lost <- gameLost gs
ot <- gs^.overtimeFlag
tScore <- teamScore gs
oScore <- otherScore gs
let
hw = if gType == HomeGame && won then 1 else 0
hl = if gType == HomeGame && lost 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
al = if gType == AwayGame && lost 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
& database.dbHomeGameStats
%~ (gmsWins +~ hw)
. (gmsLosses +~ hl)
. (gmsOvertime +~ hot)
. (gmsGoalsFor +~ hgf)
. (gmsGoalsAgainst +~ hga)
& database.dbAwayGameStats
%~ (gmsWins +~ aw)
. (gmsLosses +~ al)
. (gmsOvertime +~ aot)
. (gmsGoalsFor +~ agf)
. (gmsGoalsAgainst +~ aga)
-- | Validates the game date
validateGameDate :: ProgState -> ProgState
@@ -111,3 +130,83 @@ validateGameDate s = fromMaybe s $ do
. (gameMonth .~ Nothing)
. (gameDay .~ Nothing)
else s
-- | Starts player creation mode
createPlayer :: ProgState -> ProgState
createPlayer = let
cb = modify $ progMode .~ MainMenu
cps
= newCreatePlayerState
& cpsSuccessCallback .~ cb
& cpsFailureCallback .~ cb
in progMode .~ CreatePlayer cps
-- | Adds the entered player to the roster
addPlayer :: ProgState -> ProgState
addPlayer s = fromMaybe s $ do
let cps = s^.progMode.createPlayerStateL
num <- cps^.cpsNumber
let
name = cps^.cpsName
pos = cps^.cpsPosition
player = newPlayer num name pos
Just $ s & database.dbPlayers
%~ (++[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
awardGoal
:: Int
-- ^ The player's index number
-> ProgState
-> ProgState
awardGoal n ps = ps
& progMode.gameStateL.gamePlayerStats %~
(\m -> let
stats = M.findWithDefault newPlayerStats n m
in M.insert n (stats & psGoals %~ succ) m)
& database.dbPlayers %~ map
(\(i, p) -> if i == n
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)

View File

@@ -24,3 +24,19 @@ module Mtlstats.Config where
-- | The name of the team whose stats we're tracking
myTeam :: String
myTeam = "MONTREAL"
-- | The maximum number of function keys
maxFunKeys :: Int
maxFunKeys = 9
-- | The application name
appName :: String
appName = "mtlstats"
-- | The database filename
dbFname :: String
dbFname = "database.json"
-- | The maximum number of assists
maxAssists :: Int
maxAssists = 2

View File

@@ -21,39 +21,63 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
module Mtlstats.Control (dispatch) where
import Control.Monad (when)
import Control.Monad.Trans.State (modify)
import Control.Monad (join, when)
import Control.Monad.Trans.State (gets, modify)
import Data.Char (toUpper)
import Data.Maybe (fromJust)
import Lens.Micro ((^.), (.~))
import Lens.Micro.Extras (view)
import qualified UI.NCurses as C
import Mtlstats.Actions
import Mtlstats.Format
import Mtlstats.Handlers
import Mtlstats.Menu
import Mtlstats.Prompt
import Mtlstats.Report
import Mtlstats.Types
import Mtlstats.Util
-- | Reads the program state and returs the apropriate controller to
-- run
dispatch :: ProgState -> Controller
dispatch s = case s^.progMode of
MainMenu -> mainMenuC
NewSeason -> newSeasonC
NewGame gs
| null $ gs^.gameYear -> gameYearC
| null $ gs^.gameMonth -> gameMonthC
| null $ gs^.gameDay -> gameDayC
| null $ gs^.gameType -> gameTypeC
| null $ gs^.otherTeam -> otherTeamC
| null $ gs^.homeScore -> homeScoreC
| null $ gs^.awayScore -> awayScoreC
| null $ gs^.overtimeFlag -> overtimeFlagC
| not $ gs^.dataVerified -> verifyDataC
| fromJust (unaccountedPoints gs) -> goalInput gs
| otherwise -> reportC
CreatePlayer cps
| null $ cps^.cpsNumber -> getPlayerNumC
| null $ cps^.cpsName -> getPlayerNameC
| null $ cps^.cpsPosition -> getPlayerPosC
| otherwise -> confirmCreatePlayerC
MainMenu -> Controller
mainMenuC :: Controller
mainMenuC = Controller
{ drawController = const $ drawMenu mainMenu
, handleController = menuHandler mainMenu
}
NewSeason -> Controller
newSeasonC :: Controller
newSeasonC = Controller
{ drawController = const $ drawMenu newSeasonMenu
, handleController = \e -> do
menuHandler newSeasonMenu e
return True
}
NewGame gs
| null $ gs^.gameYear -> Controller
gameYearC :: Controller
gameYearC = Controller
{ drawController = \s -> do
header s
drawPrompt gameYearPrompt s
@@ -62,7 +86,8 @@ dispatch s = case s^.progMode of
return True
}
| null $ gs^.gameMonth -> Controller
gameMonthC :: Controller
gameMonthC = Controller
{ drawController = \s -> do
header s
drawMenu gameMonthMenu
@@ -71,7 +96,8 @@ dispatch s = case s^.progMode of
return True
}
| null $ gs^.gameDay -> Controller
gameDayC :: Controller
gameDayC = Controller
{ drawController = \s -> do
header s
drawPrompt gameDayPrompt s
@@ -81,7 +107,8 @@ dispatch s = case s^.progMode of
return True
}
| null $ gs^.gameType -> Controller
gameTypeC :: Controller
gameTypeC = Controller
{ drawController = \s -> do
header s
drawMenu gameTypeMenu
@@ -90,7 +117,8 @@ dispatch s = case s^.progMode of
return True
}
| null $ gs^.otherTeam -> Controller
otherTeamC :: Controller
otherTeamC = Controller
{ drawController = \s -> do
header s
drawPrompt otherTeamPrompt s
@@ -99,7 +127,8 @@ dispatch s = case s^.progMode of
return True
}
| null $ gs^.homeScore -> Controller
homeScoreC :: Controller
homeScoreC = Controller
{ drawController = \s -> do
header s
drawPrompt homeScorePrompt s
@@ -108,29 +137,112 @@ dispatch s = case s^.progMode of
return True
}
| null $ gs^.awayScore -> Controller
awayScoreC :: Controller
awayScoreC = Controller
{ drawController = \s -> do
header s
drawPrompt awayScorePrompt s
, handleController = \e -> do
promptHandler awayScorePrompt e
modify overtimeCheck
modify updateGameStats
return True
}
| null $ gs^.overtimeFlag -> Controller
overtimeFlagC :: Controller
overtimeFlagC = Controller
{ drawController = \s -> do
header s
C.drawString "Did the game go into overtime? (Y/N)"
return C.CursorInvisible
, handleController = \e -> do
overtimePrompt e
modify updateGameStats
modify $ progMode.gameStateL.overtimeFlag .~ ynHandler e
return True
}
| otherwise -> Controller
verifyDataC :: Controller
verifyDataC = Controller
{ drawController = \s -> do
let gs = s^.progMode.gameStateL
header s
C.drawString "\n"
C.drawString $ " Date: " ++ gameDate gs ++ "\n"
C.drawString $ " Game type: " ++ show (fromJust $ gs^.gameType) ++ "\n"
C.drawString $ "Other team: " ++ gs^.otherTeam ++ "\n"
C.drawString $ "Home score: " ++ show (fromJust $ gs^.homeScore) ++ "\n"
C.drawString $ "Away score: " ++ show (fromJust $ gs^.awayScore) ++ "\n"
C.drawString $ " Overtime: " ++ show (fromJust $ gs^.overtimeFlag) ++ "\n\n"
C.drawString "Is the above information correct? (Y/N)"
return C.CursorInvisible
, handleController = \e -> do
case ynHandler e of
Just True -> do
modify $ progMode.gameStateL.dataVerified .~ True
modify updateGameStats
Just False -> modify $ progMode.gameStateL .~ newGameState
Nothing -> return ()
return True
}
goalInput :: GameState -> Controller
goalInput gs
| null (gs^.goalBy ) = recordGoalC
| not (gs^.confirmGoalDataFlag) = recordAssistC
| otherwise = confirmGoalDataC
recordGoalC :: Controller
recordGoalC = Controller
{ drawController = \s -> let
(game, goal) = gameGoal s
in drawPrompt (recordGoalPrompt game goal) s
, handleController = \e -> do
(game, goal) <- gets gameGoal
promptHandler (recordGoalPrompt game goal) e
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
}
reportC :: Controller
reportC = Controller
{ drawController = \s -> do
(_, cols) <- C.windowSize
C.drawString $ report (fromInteger $ pred cols) s
@@ -149,10 +261,58 @@ header :: ProgState -> C.Update ()
header s = C.drawString $
"*** GAME " ++ padNum 2 (s^.database.dbGames) ++ " ***\n"
overtimePrompt :: C.Event -> Action ()
overtimePrompt (C.EventCharacter c) = modify $
progMode.gameStateL.overtimeFlag .~ case toUpper c of
'Y' -> Just True
'N' -> Just False
_ -> Nothing
overtimePrompt _ = return ()
getPlayerNumC :: Controller
getPlayerNumC = Controller
{ drawController = drawPrompt playerNumPrompt
, handleController = \e -> do
promptHandler playerNumPrompt e
return True
}
getPlayerNameC :: Controller
getPlayerNameC = Controller
{ drawController = drawPrompt playerNamePrompt
, handleController = \e -> do
promptHandler playerNamePrompt e
return True
}
getPlayerPosC :: Controller
getPlayerPosC = Controller
{ drawController = drawPrompt playerPosPrompt
, handleController = \e -> do
promptHandler playerPosPrompt e
return True
}
confirmCreatePlayerC :: Controller
confirmCreatePlayerC = Controller
{ drawController = \s -> do
let cps = s^.progMode.createPlayerStateL
C.drawString $ " Player number: " ++ show (fromJust $ cps^.cpsNumber) ++ "\n"
C.drawString $ " Player name: " ++ cps^.cpsName ++ "\n"
C.drawString $ "Player position: " ++ cps^.cpsPosition ++ "\n\n"
C.drawString "Create player: are you sure? (Y/N)"
return C.CursorInvisible
, handleController = \e -> do
case ynHandler e of
Just True -> do
modify addPlayer
join $ gets $ view $ progMode.createPlayerStateL.cpsSuccessCallback
Just False ->
join $ gets $ view $ progMode.createPlayerStateL.cpsFailureCallback
Nothing -> return ()
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)

33
src/Mtlstats/Handlers.hs Normal file
View File

@@ -0,0 +1,33 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program 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.
This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
-}
module Mtlstats.Handlers (ynHandler) where
import Data.Char (toUpper)
import qualified UI.NCurses as C
-- | Handler for a yes/no prompt
ynHandler :: C.Event -> Maybe Bool
ynHandler (C.EventCharacter c) = case toUpper c of
'Y' -> Just True
'N' -> Just False
_ -> Nothing
ynHandler _ = Nothing

View File

@@ -30,12 +30,21 @@ module Mtlstats.Menu (
gameTypeMenu
) where
import Control.Monad.Trans.State (modify)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.State (gets, modify)
import Data.Aeson (encodeFile)
import Data.Char (toUpper)
import Lens.Micro ((^.), (.~), (?~))
import Lens.Micro.Extras (view)
import System.EasyFile
( createDirectoryIfMissing
, getAppUserDataDirectory
, (</>)
)
import qualified UI.NCurses as C
import Mtlstats.Actions
import Mtlstats.Config
import Mtlstats.Types
import Mtlstats.Types.Menu
@@ -60,7 +69,15 @@ mainMenu = Menu "*** MAIN MENU ***" True
modify startNewSeason >> return True
, MenuItem '2' "New Game" $
modify startNewGame >> return True
, MenuItem '3' "Exit" $
, MenuItem '3' "Create Player" $
modify createPlayer >> return True
, MenuItem '4' "Exit" $ do
db <- gets $ view database
liftIO $ do
dir <- getAppUserDataDirectory appName
let dbFile = dir </> dbFname
createDirectoryIfMissing True dir
encodeFile dbFile db
return False
]

View File

@@ -19,6 +19,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
-}
{-# LANGUAGE LambdaCase #-}
module Mtlstats.Prompt (
-- * Prompt Functions
drawPrompt,
@@ -30,20 +32,29 @@ module Mtlstats.Prompt (
gameDayPrompt,
otherTeamPrompt,
homeScorePrompt,
awayScorePrompt
awayScorePrompt,
playerNumPrompt,
playerNamePrompt,
playerPosPrompt,
selectPlayerPrompt,
recordGoalPrompt,
recordAssistPrompt
) where
import Control.Monad (when)
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
import Mtlstats.Actions
import Mtlstats.Config
import Mtlstats.Format
import Mtlstats.Types
import Mtlstats.Util
-- | Draws the prompt to the screen
drawPrompt :: Prompt -> ProgState -> C.Update C.CursorMode
@@ -63,8 +74,8 @@ promptHandler p (C.EventCharacter c) = let
modify $ addChar c'
promptHandler _ (C.EventSpecialKey C.KeyBackspace) =
modify removeChar
promptHandler p (C.EventSpecialKey (C.KeyFunction k)) =
promptFunctionKey p k
promptHandler p (C.EventSpecialKey k) =
promptSpecialKey p k
promptHandler _ _ = return ()
-- | Builds a string prompt
@@ -78,7 +89,7 @@ strPrompt pStr act = Prompt
{ promptDrawer = drawSimplePrompt pStr
, promptCharCheck = const True
, promptAction = act
, promptFunctionKey = const $ return ()
, promptSpecialKey = const $ return ()
}
-- | Builds a numeric prompt
@@ -92,7 +103,7 @@ numPrompt pStr act = Prompt
{ promptDrawer = drawSimplePrompt pStr
, promptCharCheck = isDigit
, promptAction = \inStr -> forM_ (readMaybe inStr) act
, promptFunctionKey = const $ return ()
, promptSpecialKey = const $ return ()
}
-- | Prompts for the game year
@@ -120,5 +131,108 @@ awayScorePrompt :: Prompt
awayScorePrompt = numPrompt "Away score: " $
modify . (progMode.gameStateL.awayScore ?~)
-- | Prompts for a new player's number
playerNumPrompt :: Prompt
playerNumPrompt = numPrompt "Player number: " $
modify . (progMode.createPlayerStateL.cpsNumber ?~)
-- | Prompts for a new player's name
playerNamePrompt :: Prompt
playerNamePrompt = strPrompt "Player name: " $
modify . (progMode.createPlayerStateL.cpsName .~)
-- | Prompts for a new player's position
playerPosPrompt :: Prompt
playerPosPrompt = strPrompt "Player position: " $
modify . (progMode.createPlayerStateL.cpsPosition .~)
-- | Selects a player (creating one if necessary)
selectPlayerPrompt
:: String
-- ^ The prompt string
-> (Maybe Int -> Action ())
-- ^ The callback to run (takes the index number of the payer as
-- input)
-> Prompt
selectPlayerPrompt pStr callback = Prompt
{ promptDrawer = \s -> do
let sStr = s^.inputBuffer
C.drawString pStr
C.drawString sStr
(row, col) <- C.cursorPosition
C.drawString "\n\nPlayer select:\n"
let sel = zip [1..maxFunKeys] $ playerSearch sStr $ s^.database.dbPlayers
mapM_
(\(n, (_, p)) -> C.drawString $
"F" ++ show n ++ ") " ++ p^.pName ++ " (" ++ show (p^.pNumber) ++ ")\n")
sel
C.moveCursor row col
, promptCharCheck = const True
, promptAction = \sStr -> if null sStr
then callback Nothing
else do
players <- gets $ view $ database.dbPlayers
case playerSearchExact sStr players of
Just (n, _) -> callback $ Just n
Nothing -> do
mode <- gets $ view progMode
let
cps
= newCreatePlayerState
& cpsName .~ sStr
& cpsSuccessCallback .~ do
modify $ progMode .~ mode
pIndex <- pred . length <$> gets (view $ database.dbPlayers)
callback $ Just pIndex
& cpsFailureCallback .~ do
modify $ progMode .~ mode
callback Nothing
modify $ progMode .~ CreatePlayer cps
, promptSpecialKey = \case
C.KeyFunction n -> do
sStr <- gets $ view inputBuffer
players <- gets $ view $ database.dbPlayers
modify $ inputBuffer .~ ""
let
fKey = pred $ fromIntegral n
options = playerSearch sStr players
sel = fst <$> nth fKey options
callback sel
_ -> return ()
}
-- | Prompts for the player who scored the goal
recordGoalPrompt
:: Int
-- ^ The game number
-> Int
-- ^ The goal number
-> Prompt
recordGoalPrompt game goal = selectPlayerPrompt
( "*** GAME " ++ padNum 2 game ++ " ***\n"
++ "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
drawSimplePrompt :: String -> ProgState -> C.Update ()
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) where
module Mtlstats.Report (report, gameDate) where
import qualified Data.Map as M
import Data.Maybe (fromMaybe)
import Lens.Micro ((^.))
import Mtlstats.Config
import Mtlstats.Format
import Mtlstats.Types
import Mtlstats.Util
-- | Generates the report
report
@@ -40,18 +42,21 @@ report width s = unlines $ fromMaybe [] $ do
db = s^.database
gs = s^.progMode.gameStateL
gNum = db^.dbGames
date = gameDate gs
hTeam = homeTeam gs
aTeam = awayTeam gs
hStats = db^.dbHomeGameStats
aStats = db^.dbAwayGameStats
tStats = addGameStats hStats aStats
players = db^.dbPlayers
hScore <- gs^.homeScore
aScore <- gs^.awayScore
month <- month <$> gs^.gameMonth
day <- padNum 2 <$> gs^.gameDay
year <- show <$> gs^.gameYear
let date = month ++ " " ++ day ++ " " ++ year
Just
pStats <- mapM
(\(n, stats) -> do
player <- nth n players
Just (player, stats))
(M.toList $ gs^.gamePlayerStats)
Just $
[ overlay
("GAME NUMBER " ++ padNum 2 gNum)
(centre width
@@ -66,6 +71,8 @@ report width s = unlines $ fromMaybe [] $ do
++ right 4 "W"
++ right 4 "L"
++ right 4 "OT"
++ right 4 "GF"
++ right 4 "GA"
++ right 4 "P"
, centre width
$ left 11 "HOME"
@@ -75,11 +82,37 @@ report width s = unlines $ fromMaybe [] $ do
++ showStats aStats
, centre width
$ replicate 11 ' '
++ replicate (2 + 4 * 4) '-'
++ replicate (2 + 4 * 6) '-'
, centre width
$ left 11 "TOTALS"
++ showStats tStats
]
, ""
, centre width "GAME STATISTICS"
, ""
, centre width
$ "NO. "
++ left 20 "PLAYER"
++ right 3 "G"
++ right 6 "A"
++ right 6 "P"
++ right 6 "PM"
] ++ map
(\(p, stats) -> centre width
$ right 2 (show $ p^.pNumber)
++ " "
++ left 20 (p^.pName)
++ right 3 (show $ stats^.psGoals)
++ right 6 (show $ stats^.psAssists)
++ right 6 (show $ pPoints stats)
++ right 6 (show $ stats^.psPMin))
pStats
gameDate :: GameState -> String
gameDate gs = fromMaybe "" $ do
year <- show <$> gs^.gameYear
month <- month <$> gs^.gameMonth
day <- padNum 2 <$> gs^.gameDay
Just $ month ++ " " ++ day ++ " " ++ year
showStats :: GameStats -> String
showStats gs
@@ -87,4 +120,6 @@ showStats gs
++ right 4 (show $ gs^.gmsWins)
++ right 4 (show $ gs^.gmsLosses)
++ right 4 (show $ gs^.gmsOvertime)
++ right 4 (show $ gs^.gmsGoalsFor)
++ right 4 (show $ gs^.gmsGoalsAgainst)
++ right 4 (show $ gmsPoints gs)

View File

@@ -26,9 +26,10 @@ module Mtlstats.Types (
Controller (..),
Action,
ProgState (..),
GameState (..),
ProgMode (..),
GameState (..),
GameType (..),
CreatePlayerState (..),
Database (..),
Player (..),
PlayerStats (..),
@@ -43,6 +44,7 @@ module Mtlstats.Types (
inputBuffer,
-- ** ProgMode Lenses
gameStateL,
createPlayerStateL,
-- ** GameState Lenses
gameYear,
gameMonth,
@@ -52,6 +54,18 @@ module Mtlstats.Types (
homeScore,
awayScore,
overtimeFlag,
dataVerified,
pointsAccounted,
goalBy,
assistsBy,
gamePlayerStats,
confirmGoalDataFlag,
-- ** CreatePlayerState Lenses
cpsNumber,
cpsName,
cpsPosition,
cpsSuccessCallback,
cpsFailureCallback,
-- ** Database Lenses
dbPlayers,
dbGoalies,
@@ -85,9 +99,12 @@ module Mtlstats.Types (
gmsWins,
gmsLosses,
gmsOvertime,
gmsGoalsFor,
gmsGoalsAgainst,
-- * Constructors
newProgState,
newGameState,
newCreatePlayerState,
newDatabase,
newPlayer,
newPlayerStats,
@@ -103,12 +120,17 @@ module Mtlstats.Types (
gameWon,
gameLost,
gameTied,
unaccountedPoints,
-- ** GameStats Helpers
gmsGames,
gmsPoints,
addGameStats,
-- ** Player Helpers
pPoints
pPoints,
playerSearch,
playerSearchExact,
modifyPlayer,
playerSummary
) where
import Control.Monad.Trans.State (StateT)
@@ -124,6 +146,9 @@ import Data.Aeson
, (.:)
, (.=)
)
import Data.List (isInfixOf)
import qualified Data.Map as M
import Data.Maybe (listToMaybe)
import Lens.Micro (Lens', lens, (&), (^.), (.~))
import Lens.Micro.TH (makeLenses)
import qualified UI.NCurses as C
@@ -149,7 +174,20 @@ data ProgState = ProgState
-- ^ The program's mode
, _inputBuffer :: String
-- ^ Buffer for user input
} deriving (Eq, Show)
}
-- | The program mode
data ProgMode
= MainMenu
| NewSeason
| NewGame GameState
| CreatePlayer CreatePlayerState
instance Show ProgMode where
show MainMenu = "MainMenu"
show NewSeason = "NewSeason"
show (NewGame _) = "NewGame"
show (CreatePlayer _) = "CreatePlayer"
-- | The game state
data GameState = GameState
@@ -169,21 +207,42 @@ data GameState = GameState
-- ^ The away team's score
, _overtimeFlag :: Maybe Bool
-- ^ Indicates whether or not the game went into overtime
, _dataVerified :: Bool
-- ^ Set to 'True' when the user confirms the entered data
, _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
} deriving (Eq, Show)
-- | The program mode
data ProgMode
= MainMenu
| NewSeason
| NewGame GameState
deriving (Eq, Show)
-- | The type of game
data GameType
= HomeGame
| AwayGame
deriving (Eq, Show)
-- | Player creation status
data CreatePlayerState = CreatePlayerState
{ _cpsNumber :: Maybe Int
-- ^ The player's number
, _cpsName :: String
-- ^ The player's name
, _cpsPosition :: String
-- ^ The player's position
, _cpsSuccessCallback :: Action ()
-- ^ The function to call on success
, _cpsFailureCallback :: Action ()
-- ^ The function to call on failure
}
-- | Represents the database
data Database = Database
{ _dbPlayers :: [Player]
@@ -372,6 +431,10 @@ data GameStats = GameStats
-- ^ Games lost
, _gmsOvertime :: Int
-- ^ Games lost in overtime
, _gmsGoalsFor :: Int
-- ^ Goals for the team
, _gmsGoalsAgainst :: Int
-- ^ Goals against the team
} deriving (Eq, Show)
instance FromJSON GameStats where
@@ -379,17 +442,23 @@ instance FromJSON GameStats where
<$> v .: "wins"
<*> v .: "losses"
<*> v .: "overtime"
<*> v .: "goals_for"
<*> v .: "goals_against"
instance ToJSON GameStats where
toJSON (GameStats w l ot) = object
toJSON (GameStats w l ot gf ga) = object
[ "wins" .= w
, "losses" .= l
, "overtime" .= ot
, "goals_for" .= gf
, "goals_against" .= ga
]
toEncoding (GameStats w l ot) = pairs $
toEncoding (GameStats w l ot gf ga) = pairs $
"wins" .= w <>
"losses" .= l <>
"overtime" .= ot
"overtime" .= ot <>
"goals_for" .= gf <>
"goals_against" .= ga
-- | Defines a user prompt
data Prompt = Prompt
@@ -399,12 +468,13 @@ data Prompt = Prompt
-- ^ Determines whether or not the character is valid
, promptAction :: String -> Action ()
-- ^ Action to perform when the value is entered
, promptFunctionKey :: Integer -> Action ()
-- ^ Action to perform when a function key is pressed
, promptSpecialKey :: C.Key -> Action ()
-- ^ Action to perform when a special key is pressed
}
makeLenses ''ProgState
makeLenses ''GameState
makeLenses ''CreatePlayerState
makeLenses ''Database
makeLenses ''Player
makeLenses ''PlayerStats
@@ -419,6 +489,13 @@ gameStateL = lens
_ -> newGameState)
(\_ gs -> NewGame gs)
createPlayerStateL :: Lens' ProgMode CreatePlayerState
createPlayerStateL = lens
(\case
CreatePlayer cps -> cps
_ -> newCreatePlayerState)
(\_ cps -> CreatePlayer cps)
-- | Constructor for a 'ProgState'
newProgState :: ProgState
newProgState = ProgState
@@ -438,6 +515,22 @@ newGameState = GameState
, _homeScore = Nothing
, _awayScore = Nothing
, _overtimeFlag = Nothing
, _dataVerified = False
, _pointsAccounted = 0
, _goalBy = Nothing
, _assistsBy = []
, _gamePlayerStats = M.empty
, _confirmGoalDataFlag = False
}
-- | Constructor for a 'CreatePlayerState'
newCreatePlayerState :: CreatePlayerState
newCreatePlayerState = CreatePlayerState
{ _cpsNumber = Nothing
, _cpsName = ""
, _cpsPosition = ""
, _cpsSuccessCallback = return ()
, _cpsFailureCallback = return ()
}
-- | Constructor for a 'Database'
@@ -507,6 +600,8 @@ newGameStats = GameStats
{ _gmsWins = 0
, _gmsLosses = 0
, _gmsOvertime = 0
, _gmsGoalsFor = 0
, _gmsGoalsAgainst = 0
}
-- | Determines the team's score
@@ -545,15 +640,26 @@ gameWon gs = (>) <$> teamScore gs <*> otherScore gs
-- | Checks if the game was lost
gameLost :: GameState -> Maybe Bool
gameLost gs = (<) <$> teamScore gs <*> otherScore gs
gameLost gs = do
ot <- gs^.overtimeFlag
team <- teamScore gs
other <- otherScore gs
Just $ not ot && other > team
-- | Checks if the game has tied
gameTied :: GameState -> Maybe Bool
gameTied gs = (==) <$> gs^.homeScore <*> gs^.awayScore
-- | Checks for unaccounted points
unaccountedPoints :: GameState -> Maybe Bool
unaccountedPoints gs = do
scored <- teamScore gs
let accounted = gs^.pointsAccounted
Just $ scored > accounted
-- | Calculates the number of games played
gmsGames :: GameStats -> Int
gmsGames gs = gs^.gmsWins + gs^.gmsLosses
gmsGames gs = gs^.gmsWins + gs^.gmsLosses + gs^.gmsOvertime
-- | Calculates the number of points
gmsPoints :: GameStats -> Int
@@ -565,8 +671,57 @@ addGameStats s1 s2 = GameStats
{ _gmsWins = s1^.gmsWins + s2^.gmsWins
, _gmsLosses = s1^.gmsLosses + s2^.gmsLosses
, _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
playerSearch
:: String
-- ^ The search string
-> [Player]
-- ^ The list of players to search
-> [(Int, Player)]
-- ^ The matching players with their index numbers
playerSearch sStr =
filter (match sStr) .
zip [0..]
where match sStr (_, p) = sStr `isInfixOf` (p^.pName)
-- | Searches for a player by exact match on name
playerSearchExact
:: String
-- ^ The player's name
-> [Player]
-- ^ The list of players to search
-> Maybe (Int, Player)
-- ^ The player's index and value
playerSearchExact sStr =
listToMaybe .
filter (match sStr) .
zip [0..]
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

29
src/Mtlstats/Util.hs Normal file
View File

@@ -0,0 +1,29 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program 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.
This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
-}
module Mtlstats.Util (nth) where
nth :: Int -> [a] -> Maybe a
nth _ [] = Nothing
nth n (x:xs)
| n == 0 = Just x
| n < 0 = Nothing
| otherwise = nth (pred n) xs

View File

@@ -22,9 +22,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
module ActionsSpec (spec) where
import Control.Monad (replicateM)
import qualified Data.Map as M
import Lens.Micro ((^.), (&), (.~), (?~), (%~))
import System.Random (randomRIO)
import Test.Hspec (Spec, context, describe, it, shouldBe, shouldNotBe)
import Test.Hspec (Spec, context, describe, it, runIO, shouldBe, shouldNotBe)
import Mtlstats.Actions
import Mtlstats.Types
@@ -39,6 +40,12 @@ spec = describe "Mtlstats.Actions" $ do
overtimeCheckSpec
updateGameStatsSpec
validateGameDateSpec
createPlayerSpec
addPlayerSpec
recordGoalAssistsSpec
awardGoalSpec
awardAssistSpec
resetGoalDataSpec
startNewSeasonSpec :: Spec
startNewSeasonSpec = describe "startNewSeason" $ do
@@ -48,7 +55,7 @@ startNewSeasonSpec = describe "startNewSeason" $ do
& startNewSeason
it "should set the progState to NewSeason" $
s ^. progMode `shouldBe` NewSeason
show (s^.progMode) `shouldBe` "NewSeason"
it "should set the number of games to 0" $
s ^. database . dbGames `shouldBe` 0
@@ -61,7 +68,7 @@ startNewGameSpec = describe "startNewGame" $ do
s ^. database . dbGames `shouldBe` 1
it "should set the mode to NewGame" $
s ^. progMode `shouldBe` NewGame newGameState
show (s^.progMode) `shouldBe` "NewGame"
resetYtdSpec :: Spec
resetYtdSpec = describe "resetYtd" $
@@ -193,6 +200,8 @@ updateGameStatsSpec = describe "updateGameStats" $ do
& gmsWins .~ 1
& gmsLosses .~ 1
& gmsOvertime .~ 1
& gmsGoalsFor .~ 1
& gmsGoalsAgainst .~ 1
s t h a o = newProgState
& progMode.gameStateL
@@ -204,71 +213,79 @@ updateGameStatsSpec = describe "updateGameStats" $ do
%~ (dbHomeGameStats .~ baseStats)
. (dbAwayGameStats .~ baseStats)
db hw hl ho aw al ao = newDatabase
db hw hl ho hf ha aw al ao af aa = newDatabase
& dbHomeGameStats
%~ (gmsWins .~ hw)
. (gmsLosses .~ hl)
. (gmsOvertime .~ ho)
. (gmsGoalsFor .~ hf)
. (gmsGoalsAgainst .~ ha)
& dbAwayGameStats
%~ (gmsWins .~ aw)
. (gmsLosses .~ al)
. (gmsOvertime .~ ao)
. (gmsGoalsFor .~ af)
. (gmsGoalsAgainst .~ aa)
context "home win" $
it "should record a home win" $ let
s' = s (Just HomeGame) (Just 2) (Just 1) (Just False)
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" $
it "should record a home loss" $ let
s' = s (Just HomeGame) (Just 1) (Just 2) (Just False)
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" $
it "should record a home loss and overtime" $ let
it "should record a home overtime" $ let
s' = s (Just HomeGame) (Just 1) (Just 2) (Just True)
db' = updateGameStats s' ^. database
in db' `shouldBe` db 1 2 2 1 1 1
in db' `shouldBe` db 1 1 2 2 3 1 1 1 1 1
context "away win" $
it "should record an away win" $ let
s' = s (Just AwayGame) (Just 1) (Just 2) (Just False)
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" $
it "should record an away loss" $ let
s' = s (Just AwayGame) (Just 2) (Just 1) (Just False)
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" $
it "should record an away loss and overtime" $ let
it "should record an away overtime" $ let
s' = s (Just AwayGame) (Just 2) (Just 1) (Just True)
db' = updateGameStats s' ^. database
in db' `shouldBe` db 1 1 1 1 2 2
in db' `shouldBe` db 1 1 1 1 1 1 1 2 2 3
context "missing game type" $
it "should not change anything" $ let
s' = s Nothing (Just 1) (Just 2) (Just True)
in updateGameStats s' `shouldBe` s'
db' = updateGameStats s' ^. database
in db' `shouldBe` db 1 1 1 1 1 1 1 1 1 1
context "missing home score" $
it "should not change anything" $ let
s' = s (Just HomeGame) Nothing (Just 1) (Just True)
in updateGameStats s' `shouldBe` s'
db' = updateGameStats s' ^. database
in db' `shouldBe` db 1 1 1 1 1 1 1 1 1 1
context "missing away score" $
it "should not change anything" $ let
s' = s (Just HomeGame) (Just 1) Nothing (Just True)
in updateGameStats s' `shouldBe` s'
db' = updateGameStats s' ^. database
in db' `shouldBe` db 1 1 1 1 1 1 1 1 1 1
context "missing overtime flag" $
it "should not change anything" $ let
s' = s (Just HomeGame) (Just 1) (Just 2) Nothing
in updateGameStats s' `shouldBe` s'
db' = updateGameStats s' ^. database
in db' `shouldBe` db 1 1 1 1 1 1 1 1 1 1
validateGameDateSpec :: Spec
validateGameDateSpec = describe "validateGameDate" $ do
@@ -315,6 +332,219 @@ validateGameDateSpec = describe "validateGameDate" $ do
s^.progMode.gameStateL.gameMonth `shouldBe` Just 6
s^.progMode.gameStateL.gameDay `shouldBe` Nothing
createPlayerSpec :: Spec
createPlayerSpec = describe "createPlayer" $
it "should change the mode appropriately" $ let
s = createPlayer newProgState
in show (s^.progMode) `shouldBe` "CreatePlayer"
addPlayerSpec :: Spec
addPlayerSpec = describe "addPlayer" $ do
let
p1 = newPlayer 1 "Joe" "centre"
p2 = newPlayer 2 "Bob" "defense"
db = newDatabase
& dbPlayers .~ [p1]
s pm = newProgState
& progMode .~ pm
& database .~ db
context "data available" $
it "should create the player" $ let
s' = addPlayer $ s $ CreatePlayer $ newCreatePlayerState
& cpsNumber ?~ 2
& cpsName .~ "Bob"
& cpsPosition .~ "defense"
in s'^.database.dbPlayers `shouldBe` [p1, p2]
context "data unavailable" $
it "should not create the player" $ let
s' = addPlayer $ s MainMenu
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 = describe "awardGoal" $ do
let
joe
= newPlayer 2 "Joe" "centre"
& pYtd.psGoals .~ 1
& pLifetime.psGoals .~ 2
bob
= newPlayer 3 "Bob" "defense"
& pYtd.psGoals .~ 3
& pLifetime.psGoals .~ 4
db
= newDatabase
& dbPlayers .~ [joe, bob]
joeStats
= newPlayerStats
& psGoals .~ 1
ps
= newProgState
& progMode.gameStateL.gamePlayerStats .~ M.singleton 0 joeStats
& database .~ db
mapM_
(\(pName, pid, ytd, lt, game) ->
context pName $ do
let
ps' = awardGoal pid ps
player = (ps'^.database.dbPlayers) !! pid
gStats = (ps'^.progMode.gameStateL.gamePlayerStats) M.! pid
it ("should increment " ++ pName ++ "'s year-to-date goals") $
player^.pYtd.psGoals `shouldBe` ytd
it ("should increment " ++ pName ++ "'s lifetime goals") $
player^.pLifetime.psGoals `shouldBe` lt
it ("should increment " ++ pName ++ "'s game goals") $
gStats^.psGoals `shouldBe` game)
-- player name, player id, ytd goals, lifetime goals, game goals
[ ( "Joe", 0, 2, 3, 2 )
, ( "Bob", 1, 4, 5, 1 )
]
context "invalid index" $ let
ps' = awardGoal 2 ps
in it "should not change the database" $
ps'^.database `shouldBe` db
context "negative index" $ let
ps' = awardGoal (-1) ps
in it "should not change the database" $
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
makePlayer :: IO Player
makePlayer = Player
<$> makeNum

46
test/HandlersSpec.hs Normal file
View File

@@ -0,0 +1,46 @@
{-
mtlstats
Copyright (C) 2019 Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program 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.
This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
-}
module HandlersSpec (spec) where
import Test.Hspec (Spec, context, describe, it, shouldBe)
import qualified UI.NCurses as C
import Mtlstats.Handlers
spec :: Spec
spec = describe "Mtlstats.Handlers"
ynHandlerSpec
ynHandlerSpec :: Spec
ynHandlerSpec = describe "ynHandler" $ mapM_
(\(desc, event, expected) ->
context desc $
it ("should be " ++ show expected) $
ynHandler event `shouldBe` expected)
-- description, event, expected
[ ( "Y pressed", C.EventCharacter 'Y', Just True )
, ( "y pressed", C.EventCharacter 'y', Just True )
, ( "N pressed", C.EventCharacter 'N', Just False )
, ( "n pressed", C.EventCharacter 'n', Just False )
, ( "x pressed", C.EventCharacter 'x', Nothing )
, ( "other event", C.EventResized, Nothing )
]

47
test/ReportSpec.hs Normal file
View File

@@ -0,0 +1,47 @@
{-
mtlstats
Copyright (C) 2019 Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program 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.
This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
-}
module ReportSpec (spec) where
import Lens.Micro ((&), (?~), (%~))
import Test.Hspec (Spec, context, describe, it, shouldBe)
import Mtlstats.Report
import Mtlstats.Types
spec :: Spec
spec = describe "Mtlstats.Report"
gameDateSpec
gameDateSpec :: Spec
gameDateSpec = describe "gameDate" $ do
context "valid gameDate" $
it "should format the date" $ let
gs = newGameState
& gameYear ?~ 1980
& gameMonth ?~ 6
& gameDay ?~ 25
in gameDate gs `shouldBe` "JUN 25 1980"
context "invalid date" $
it "should return an empty string" $
gameDate newGameState `shouldBe` ""

View File

@@ -23,10 +23,16 @@ import Test.Hspec (hspec)
import qualified ActionsSpec as Actions
import qualified FormatSpec as Format
import qualified HandlersSpec as Handlers
import qualified ReportSpec as Report
import qualified TypesSpec as Types
import qualified UtilSpec as Util
main :: IO ()
main = hspec $ do
Types.spec
Actions.spec
Format.spec
Handlers.spec
Report.spec
Util.spec

View File

@@ -42,6 +42,7 @@ spec = describe "Mtlstats.Types" $ do
gameStatsSpec
databaseSpec
gameStateLSpec
createPlayerStateLSpec
teamScoreSpec
otherScoreSpec
homeTeamSpec
@@ -49,10 +50,15 @@ spec = describe "Mtlstats.Types" $ do
gameWonSpec
gameLostSpec
gameTiedSpec
unaccountedPointsSpec
gmsGamesSpec
gmsPointsSpec
addGameStatsSpec
pPointsSpec
playerSearchSpec
playerSearchExactSpec
modifyPlayerSpec
playerSummarySpec
Menu.spec
playerSpec :: Spec
@@ -81,6 +87,34 @@ gameStateLSpec = describe "gameStateL" $ lensSpec gameStateL
]
where gs t = newGameState & gameType ?~ t
createPlayerStateLSpec :: Spec
createPlayerStateLSpec = describe "createPlayerStateL" $ do
context "getters" $ do
context "state missing" $ let
pm = MainMenu
cps = pm^.createPlayerStateL
in it "should not have a number" $
cps^.cpsNumber `shouldBe` Nothing
context "existing state" $ let
pm = CreatePlayer $ newCreatePlayerState & cpsNumber ?~ 1
cps = pm^.createPlayerStateL
in it "should have a number of 1" $
cps^.cpsNumber `shouldBe` Just 1
context "setters" $ do
context "state missing" $ let
pm = MainMenu
pm' = pm & createPlayerStateL.cpsNumber ?~ 1
in it "should set the player number to 1" $
pm'^.createPlayerStateL.cpsNumber `shouldBe` Just 1
context "existing state" $ let
pm = CreatePlayer $ newCreatePlayerState & cpsNumber ?~ 1
pm' = pm & createPlayerStateL.cpsNumber ?~ 2
in it "should set the player number to 2" $
pm'^.createPlayerStateL.cpsNumber `shouldBe` Just 2
teamScoreSpec :: Spec
teamScoreSpec = describe "teamScore" $ do
let
@@ -226,6 +260,8 @@ gameStats n = GameStats
{ _gmsWins = n
, _gmsLosses = n + 1
, _gmsOvertime = n + 2
, _gmsGoalsFor = n + 3
, _gmsGoalsAgainst = n + 4
}
gameStatsJSON :: Int -> Value
@@ -233,6 +269,8 @@ gameStatsJSON n = Object $ HM.fromList
[ ( "wins", toJSON n )
, ( "losses", toJSON $ n + 1 )
, ( "overtime", toJSON $ n + 2 )
, ( "goals_for", toJSON $ n + 3 )
, ( "goals_against", toJSON $ n + 4 )
]
db :: Database
@@ -320,30 +358,36 @@ gameWonSpec = describe "gameWon" $ mapM_
gameLostSpec :: Spec
gameLostSpec = describe "gameLost" $ mapM_
(\(t, h, a, expected) -> let
(\(t, h, a, ot, expected) -> let
desc = "game type: " ++ show t ++
", home score: " ++ show h ++
", away score: " ++ show a
", away score: " ++ show a ++
", overtimr flag: " ++ show ot
gs = newGameState
& gameType .~ t
& homeScore .~ h
& awayScore .~ a
& overtimeFlag .~ ot
in context desc $
it ("should be " ++ show expected) $
gameLost gs `shouldBe` expected)
-- gameType, homeScore, awayScore, expected
[ ( Just HomeGame, Just 1, Just 1, Just False )
, ( Just HomeGame, Just 1, Just 2, Just True )
, ( Just HomeGame, Just 2, Just 1, Just False )
, ( Just AwayGame, Just 1, Just 1, Just False )
, ( Just AwayGame, Just 1, Just 2, Just False )
, ( Just AwayGame, Just 2, Just 1, Just True )
, ( Nothing, Just 1, Just 2, Nothing )
, ( Just HomeGame, Nothing, Just 1, Nothing )
, ( Just AwayGame, Nothing, Just 1, Nothing )
, ( Just HomeGame, Just 1, Nothing, Nothing )
, ( Just AwayGame, Just 1, Nothing, Nothing )
, ( Nothing, Nothing, Nothing, Nothing )
-- gameType, homeScore, awayScore, overtimeFlag, expected
[ ( Just HomeGame, Just 1, Just 1, Just False, Just False )
, ( Just HomeGame, Just 1, Just 2, Just False, Just True )
, ( Just HomeGame, Just 1, Just 2, Just True, Just False )
, ( Just HomeGame, Just 2, Just 1, Just False, Just False )
, ( Just AwayGame, Just 1, Just 1, Just False, Just False )
, ( Just AwayGame, Just 1, Just 2, Just False, Just False )
, ( Just AwayGame, Just 2, Just 1, Just False, Just True )
, ( Just AwayGame, Just 2, Just 1, Just True, Just False )
, ( Nothing, Just 1, Just 2, Just False, Nothing )
, ( Just HomeGame, Nothing, Just 1, Just False, Nothing )
, ( Just AwayGame, Nothing, Just 1, Just False, Nothing )
, ( Just HomeGame, Just 1, Nothing, Just False, Nothing )
, ( Just AwayGame, Just 1, Nothing, Just False, Nothing )
, ( Just HomeGame, Just 1, Just 2, Nothing, Nothing )
, ( Just AwayGame, Just 1, Just 2, Nothing, Nothing )
, ( Nothing, Nothing, Nothing, Just False, Nothing )
]
gameTiedSpec :: Spec
@@ -364,33 +408,65 @@ gameTiedSpec = describe "gameTied" $ mapM_
, ( Just 1, Just 2, Just False )
]
unaccountedPointsSpec :: Spec
unaccountedPointsSpec = describe "unaccounted points" $ do
context "no data" $
it "should return Nothing" $
unaccountedPoints newGameState `shouldBe` Nothing
context "unaccounted points" $
it "should return True" $ let
gs = newGameState
& gameType ?~ HomeGame
& homeScore ?~ 1
in unaccountedPoints gs `shouldBe` Just True
context "all points accounted" $
it "should return False" $ let
gs = newGameState
& gameType ?~ HomeGame
& homeScore ?~ 1
& pointsAccounted .~ 1
in unaccountedPoints gs `shouldBe` Just False
context "more points accounted" $
it "should return True" $ let
gs = newGameState
& gameType ?~ HomeGame
& homeScore ?~ 1
& pointsAccounted .~ 2
in unaccountedPoints gs `shouldBe` Just False
gmsGamesSpec :: Spec
gmsGamesSpec = describe "gmsGames" $ mapM_
(\(w, l, expected) -> let
(\(w, l, ot, expected) -> let
desc = "wins: " ++ show w ++
", losses: " ++ show l
", losses: " ++ show l ++
", overtime: " ++ show ot
gs = newGameStats
& gmsWins .~ w
& gmsLosses .~ l
& gmsOvertime .~ ot
in context desc $
it ("should be " ++ show expected) $
gmsGames gs `shouldBe` expected)
-- wins, losses, expected
[ ( 0, 0, 0 )
, ( 1, 0, 1 )
, ( 0, 1, 1 )
, ( 1, 1, 2 )
, ( 2, 3, 5 )
-- wins, losses, overtime, expected
[ ( 0, 0, 0, 0 )
, ( 1, 0, 0, 1 )
, ( 0, 1, 0, 1 )
, ( 0, 0, 1, 1 )
, ( 1, 1, 1, 3 )
, ( 2, 3, 5, 10 )
]
gmsPointsSpec :: Spec
gmsPointsSpec = describe "gmsPoints" $ mapM_
(\(w, l, ot, expected) -> let
gs = GameStats
{ _gmsWins = w
, _gmsLosses = l
, _gmsOvertime = ot
}
gs
= newGameStats
& gmsWins .~ w
& gmsLosses .~ l
& gmsOvertime .~ ot
in context (show gs) $
it ("should be " ++ show expected) $
gmsPoints gs `shouldBe` expected)
@@ -409,20 +485,26 @@ addGameStatsSpec = describe "addGameStats" $
s1 = GameStats
{ _gmsWins = 1
, _gmsLosses = 3
, _gmsOvertime = 2
, _gmsLosses = 2
, _gmsOvertime = 3
, _gmsGoalsFor = 4
, _gmsGoalsAgainst = 5
}
s2 = GameStats
{ _gmsWins = 4
, _gmsLosses = 6
, _gmsOvertime = 5
{ _gmsWins = 6
, _gmsLosses = 7
, _gmsOvertime = 8
, _gmsGoalsFor = 9
, _gmsGoalsAgainst = 10
}
expected = GameStats
{ _gmsWins = 5
{ _gmsWins = 7
, _gmsLosses = 9
, _gmsOvertime = 7
, _gmsOvertime = 11
, _gmsGoalsFor = 13
, _gmsGoalsAgainst = 15
}
in addGameStats s1 s2 `shouldBe` expected
@@ -444,3 +526,69 @@ pPointsSpec = describe "pPoints" $ mapM_
, ( 0, 1, 1 )
, ( 2, 3, 5 )
]
playerSearchSpec :: Spec
playerSearchSpec = describe "playerSearch" $ mapM_
(\(sStr, expected) -> context sStr $
it ("should return " ++ show expected) $ let
ps = [joe, bob, steve]
in playerSearch sStr ps `shouldBe` expected)
-- search, result
[ ( "Joe", [(0, joe)] )
, ( "o", [(0, joe), (1, bob)] )
, ( "e", [(0, joe), (2, steve)] )
, ( "x", [] )
]
playerSearchExactSpec :: Spec
playerSearchExactSpec = describe "playerSearchExact" $ mapM_
(\(sStr, expected) -> context sStr $
it ("should be " ++ show expected) $ let
ps = [joe, bob, steve]
in playerSearchExact sStr ps `shouldBe` expected)
-- search, result
[ ( "Joe", Just (0, joe) )
, ( "Bob", Just (1, bob) )
, ( "Steve", Just (2, steve) )
, ( "Sam", 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"
joe :: Player
joe = newPlayer 2 "Joe" "center"
bob :: Player
bob = newPlayer 3 "Bob" "defense"
steve :: Player
steve = newPlayer 5 "Steve" "forward"

44
test/UtilSpec.hs Normal file
View File

@@ -0,0 +1,44 @@
{-
mtlstats
Copyright (C) 2019 Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program 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.
This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
-}
module UtilSpec (spec) where
import Test.Hspec (Spec, context, describe, it, shouldBe)
import Mtlstats.Util
spec :: Spec
spec = describe "Mtlstats.Util"
nthSpec
nthSpec :: Spec
nthSpec = describe "nth" $ mapM_
(\(n, expected) -> context (show n) $
it ("should be " ++ show expected) $ let
xs = ["foo", "bar", "baz"]
in nth n xs `shouldBe` expected)
-- index, expected
[ ( 0, Just "foo" )
, ( 1, Just "bar" )
, ( 2, Just "baz" )
, ( 3, Nothing )
, ( -1, Nothing )
]