207 Commits

Author SHA1 Message Date
93048d6053 version 0.17.0.1 2023-06-04 18:04:41 -04:00
d55f7b1f50 force capitalization in ucStrPrompt 2023-06-04 17:53:48 -04:00
a5679cb1fc version 0.17.0 2023-06-02 15:30:44 -04:00
bdbf7daf4e Merge pull request 'switch from ncurses to brick' (#1) from brick into dev
Reviewed-on: #1
2023-06-02 15:28:41 -04:00
e0efe2657f ynHandler should ignore keypresses with modifier keys 2023-06-02 15:26:22 -04:00
886cf0b243 even more stylistic changes
I hope to God I'm done with these now.
2023-06-01 19:51:04 -04:00
251dc90cea more stylistic changes 2023-06-01 19:06:46 -04:00
17b3f9a03e minor stylistic edits 2023-06-01 18:39:46 -04:00
01457dbe6f removed signature line 2023-06-01 17:18:09 -04:00
134787e1be removed Travis CI configuration file 2023-06-01 17:15:29 -04:00
284a8c6725 various layout fixes 2023-05-31 22:19:18 -04:00
d92722be9c use Editor istead of String 2023-05-31 20:08:49 -04:00
820aab5e96 fix layout of selection prompt 2023-05-31 13:21:49 -04:00
2d5c4e6471 fixed spacing on title screen 2023-05-30 19:01:30 -04:00
097d51f34b properly centre menu headings 2023-05-30 18:56:44 -04:00
166483dc50 fixed missing blank line between menu header and options 2023-05-30 18:45:03 -04:00
08e0f96a81 cursor position fix
cursor X and Y coordinates were transposed for the simple string prompts
2023-05-30 18:30:49 -04:00
afae5ea14a updated ChangeLog 2023-05-30 18:21:56 -04:00
ea9a9c6a85 bugfix: backspace
backspace functionality was mistakenly mapped to the escape key for some reason
2023-05-30 18:11:54 -04:00
d40b56da37 bail on CTRL-C 2023-05-30 18:06:32 -04:00
5ea2d77921 bugfix: make the whole background blue 2023-05-30 17:58:45 -04:00
227401461b wip: switching from ncurses to brick 2023-05-30 17:49:35 -04:00
458554bef2 updated copyright notice on title screen 2023-05-23 17:37:38 -04:00
855854cd42 updated copyright notice 2023-05-23 17:22:14 -04:00
da6d01b258 removed Vagrant stuff 2023-05-23 15:31:04 -04:00
f869209ec6 version 0.16.1
also updated copyright notice
2021-05-08 12:19:34 -04:00
Jonathan Lamothe
c6393830e2 Merge pull request #86 from mtlstats/no-game-new-season
Don't automatically start a new game on new season
2021-05-08 12:01:17 -04:00
b054ba66f2 Don't automatically start a new game on new season 2021-05-08 11:44:38 -04:00
Jonathan Lamothe
1e0b72fc40 version 0.16.0 2020-04-15 22:26:33 -04:00
Jonathan Lamothe
2c5b4a0791 Merge pull request #85 from mtlstats/month-numbers
enter months by number
2020-04-15 22:24:14 -04:00
Jonathan Lamothe
bce31d059b enter months by number 2020-04-15 22:07:56 -04:00
Jonathan Lamothe
99baebe144 version 0.15.2 2020-04-07 21:34:05 -04:00
Jonathan Lamothe
eb3714c40a Merge pull request #84 from mtlstats/allow-ties
allow ties
2020-04-07 21:32:49 -04:00
Jonathan Lamothe
1bd3ae9564 allow ties 2020-04-07 21:30:47 -04:00
Jonathan Lamothe
2adfe9b016 version 0.15.1 2020-04-06 15:34:10 -04:00
Jonathan Lamothe
85a8e3baf1 Merge pull request #83 from mtlstats/active-player-search
only search for active players/goalies on game input
2020-04-06 15:32:29 -04:00
Jonathan Lamothe
393a2c6dc4 updated change log 2020-04-06 15:16:27 -04:00
Jonathan Lamothe
ed240c6a38 only search through active players/goalies on game input 2020-04-06 15:14:48 -04:00
Jonathan Lamothe
4f147cd5a4 implemented searchActiveGoaliePrompt 2020-04-06 15:01:26 -04:00
Jonathan Lamothe
9b6dfc4be9 implemented selectActivePlayerPrompt 2020-04-06 14:46:30 -04:00
Jonathan Lamothe
c20fb30f5b version 0.15.0 2020-03-13 00:00:52 -04:00
Jonathan Lamothe
3c0e690ed3 Merge pull request #82 from mtlstats/del-player
delete player/goalie
2020-03-12 23:59:16 -04:00
Jonathan Lamothe
f37e231623 updated change log 2020-03-12 23:53:27 -04:00
Jonathan Lamothe
fbaf2a1e60 exit properly from delete menus 2020-03-12 23:52:40 -04:00
Jonathan Lamothe
65979329bd added player/goalie delete to menus 2020-03-12 23:42:40 -04:00
Jonathan Lamothe
ded019faac implemented deleting of goalies 2020-03-12 23:37:42 -04:00
Jonathan Lamothe
1322004d38 implemented player deletion 2020-03-12 23:19:17 -04:00
Jonathan Lamothe
2cb279e7e7 implemented dropNth 2020-03-12 22:41:28 -04:00
Jonathan Lamothe
7ca66ad801 Merge pull request #81 from mtlstats/page-break
add page break to report
2020-03-12 22:14:40 -04:00
Jonathan Lamothe
82544046ce add page break to report 2020-03-12 22:09:05 -04:00
Jonathan Lamothe
95c97d722e removed unnecessary dbFname config value 2020-03-12 12:47:38 -04:00
Jonathan Lamothe
0eb46cacce Merge pull request #80 from mtlstats/season-select
Select season database on startup
2020-03-12 03:27:04 -04:00
Jonathan Lamothe
25f887a5e8 don't call modify if database isn't changing 2020-03-12 03:19:27 -04:00
Jonathan Lamothe
7ba670948b updated change log 2020-03-12 02:46:44 -04:00
Jonathan Lamothe
ca06b0570e load and save databases properly 2020-03-12 02:44:41 -04:00
Jonathan Lamothe
1e8473538a prompt for database name 2020-03-11 03:56:58 -04:00
Jonathan Lamothe
87336dcd1d control flow branch for reading database 2020-03-11 03:20:38 -04:00
Jonathan Lamothe
ffa241c1f7 added dbName field to ProgState 2020-03-11 03:09:47 -04:00
Jonathan Lamothe
f9085832f4 version 0.14.0 2020-03-05 16:56:48 -05:00
Jonathan Lamothe
e15623bde3 Merge pull request #79 from mtlstats/report-file
output report to a text file (report.txt)
2020-03-05 16:54:55 -05:00
Jonathan Lamothe
db62fbb542 output report to a text file (report.txt) 2020-03-05 16:45:40 -05:00
Jonathan Lamothe
4a8515b862 Merge pull request #78 from mtlstats/fix-shutouts
Fix shutouts
2020-03-05 05:35:17 -05:00
Jonathan Lamothe
e4c668d1e4 updated change log 2020-03-05 05:28:56 -05:00
Jonathan Lamothe
9ee33cbd03 hlint suggestions 2020-03-05 05:26:25 -05:00
Jonathan Lamothe
53c49492cb fixed shutout bug
shutouts weren't being recorded
2020-03-05 05:26:25 -05:00
Jonathan Lamothe
4d1eaa1523 Merge branch 'master' into dev 2020-03-05 05:24:57 -05:00
Jonathan Lamothe
29fae81513 version 0.13.0 2020-03-05 05:21:34 -05:00
Jonathan Lamothe
8a8a550854 Merge pull request #77 from mtlstats/vagrant
vagrant setup
2020-03-03 14:33:58 -05:00
Jonathan Lamothe
6227df8e01 vagrant setup 2020-03-03 14:23:13 -05:00
Jonathan Lamothe
0676bf4067 Merge pull request #76 from mtlstats/single-goalie
don't ask which goalie to assign the game to when there's only one
2020-02-14 23:02:47 -05:00
Jonathan Lamothe
119032ca80 don't ask which goalie to assign the game to when there's only one 2020-02-14 22:56:35 -05:00
Jonathan Lamothe
2607fc5ce8 Merge pull request #75 from mtlstats/active-check
ask whether player/goalie is active on creation
2020-02-14 00:15:17 -05:00
Jonathan Lamothe
d18cb7dd59 updated change log 2020-02-14 00:09:34 -05:00
Jonathan Lamothe
dff11a8316 assume goalie is active on creation of rookie 2020-02-14 00:08:19 -05:00
Jonathan Lamothe
747bdf8f32 assume player is active on creation of rookie 2020-02-14 00:08:19 -05:00
Jonathan Lamothe
9b07c6d249 record active flag on goalie creation 2020-02-13 23:55:00 -05:00
Jonathan Lamothe
e28ef1ff0e record active flag on player creation 2020-02-13 23:40:43 -05:00
Jonathan Lamothe
960fbb3443 display active flags in new player/goalie creation summaries 2020-02-13 23:30:31 -05:00
Jonathan Lamothe
439aab99d3 prompt whether or not a new player/goalie is active 2020-02-13 23:28:10 -05:00
Jonathan Lamothe
8d7a7997b1 created active flag controller branches 2020-02-13 23:23:18 -05:00
Jonathan Lamothe
7e409fdbd4 added cpsActiveFlag and cgsActiveFlag 2020-02-13 23:18:53 -05:00
Jonathan Lamothe
28b1fa0e06 Merge pull request #74 from mtlstats/rookie-check
Rookie check
2020-02-13 20:20:46 -05:00
Jonathan Lamothe
7c4b7331e8 updated change log 2020-02-13 20:12:00 -05:00
Jonathan Lamothe
214710661a code cleanup 2020-02-13 20:08:10 -05:00
Jonathan Lamothe
6bb4601e6b only ask for goalie lifetime stats when not rookie 2020-02-13 20:03:27 -05:00
Jonathan Lamothe
e51953650c set rookie flag appropriately on goalie creation 2020-02-13 19:45:36 -05:00
Jonathan Lamothe
14386f9c7d display whether or not goalie is a rookie on creation confirmation 2020-02-13 15:23:40 -05:00
Jonathan Lamothe
ec10aa7998 ask if a new goalie is a rookie 2020-02-13 14:57:29 -05:00
Jonathan Lamothe
fe28e96145 use promptController in Mtlstats.Control.CreateGoalie 2020-02-13 14:45:43 -05:00
Jonathan Lamothe
2941998058 only edit player lifetime stats if rookie
...on new player creation
2020-02-13 11:08:32 -05:00
Jonathan Lamothe
c22849bb3b set rookie flag on player creation 2020-02-13 10:35:35 -05:00
Jonathan Lamothe
4315b40732 added rookie flag to player creation confirmation 2020-02-13 03:24:11 -05:00
Jonathan Lamothe
fefa217df1 implemented Mtlstats.Control.CreatePlayer.getRookieFlagC
...also refactored some other controllers to use promptController
2020-02-13 02:55:51 -05:00
Jonathan Lamothe
6d77caaa14 added cpsRookieFlag and cgsRookieFlag 2020-02-13 02:31:20 -05:00
Jonathan Lamothe
a69853858d Merge pull request #73 from mtlstats/position-shortcuts
autocompletion of player positions
2020-02-13 02:20:39 -05:00
Jonathan Lamothe
045f2915e1 updated change log 2020-02-13 02:00:52 -05:00
Jonathan Lamothe
dfd226c7bd implement position selection prompt on player creation/edit 2020-02-13 01:58:59 -05:00
Jonathan Lamothe
b9d8b263df implemented posCallback 2020-02-13 01:39:56 -05:00
Jonathan Lamothe
a2968595d8 implemented posSearchExact 2020-02-12 00:27:28 -05:00
Jonathan Lamothe
25e4929f0b implemented posSearch 2020-02-11 23:58:47 -05:00
Jonathan Lamothe
457298e565 implemented getPositions 2020-02-11 23:37:43 -05:00
Jonathan Lamothe
a80eaa2a40 implemented selectPositionPrompt 2020-02-11 23:00:13 -05:00
Jonathan Lamothe
8a6cf10ad3 version 0.12.0 2020-02-07 18:26:09 -05:00
Jonathan Lamothe
bd5bc21661 Merge pull request #72 from mtlstats/allow-dashes
allow dashes in database backup files
2020-02-04 00:25:09 -05:00
Jonathan Lamothe
accc831bc5 allow dashes in database backup files 2020-02-04 00:19:15 -05:00
Jonathan Lamothe
2c78a591ca Merge pull request #71 from mtlstats/remove-edit
removed the word "edit" from edit menus (mostly)
2020-02-04 00:07:17 -05:00
Jonathan Lamothe
77dc89c76d removed the word "edit" from edit menus (mostly) 2020-02-03 23:58:30 -05:00
Jonathan Lamothe
607a78ccea Merge pull request #70 from mtlstats/sort-players
subsort players by lifetime points
2020-01-31 22:18:58 -05:00
Jonathan Lamothe
d7c5f24c26 subsort players by lifetime points 2020-01-31 22:13:30 -05:00
Jonathan Lamothe
d94c6a588e Merge pull request #69 from mtlstats/sort-goalies
sort goalies by number of minutes played
2020-01-31 21:59:48 -05:00
Jonathan Lamothe
bdbe0131d7 sort goalies by number of minutes played 2020-01-31 21:54:32 -05:00
Jonathan Lamothe
b0638b95b8 Merge pull request #68 from mtlstats/new-goalie-lifetime
Automatically set lifetime stats on new goalie creation
2020-01-31 21:31:24 -05:00
Jonathan Lamothe
ce2d32407e updated change log 2020-01-31 21:24:05 -05:00
Jonathan Lamothe
5771091f18 edit lifetime stats on goalie creation 2020-01-31 21:23:22 -05:00
Jonathan Lamothe
13a1949446 perform action on completion of goalie edit 2020-01-31 21:14:56 -05:00
Jonathan Lamothe
835cb9582b created Mtlstats.Control.CreateGoalie module 2020-01-31 00:36:16 -05:00
Jonathan Lamothe
8bc9b48aa2 added callback to EditGoalieState 2020-01-31 00:25:20 -05:00
Jonathan Lamothe
378efea24e Merge pull request #67 from mtlstats/lifetime-new-player
Lifetime new player
2020-01-30 23:47:28 -05:00
Jonathan Lamothe
6418ab0eea update change log 2020-01-30 23:41:08 -05:00
Jonathan Lamothe
95e74accd4 edit lifetime stats on new player creation 2020-01-30 23:39:20 -05:00
Jonathan Lamothe
ffc1390755 created Mtlstats.Control.CreatePlayer module 2020-01-29 12:10:57 -05:00
Jonathan Lamothe
2f0a3a5c57 perform follow-up action on player edit 2020-01-29 01:19:25 -05:00
Jonathan Lamothe
79d527866f added callback to EditPlayerState 2020-01-29 00:26:43 -05:00
Jonathan Lamothe
994087a0e6 version 0.11.0 2020-01-23 17:18:39 -05:00
Jonathan Lamothe
7fbeaac933 Merge pull request #66 from mtlstats/master-menu
Master menu
2020-01-22 21:53:09 -05:00
Jonathan Lamothe
de56f4f94d updated change log 2020-01-22 21:43:09 -05:00
Jonathan Lamothe
04ba17324e centre menus horizontally 2020-01-22 21:23:32 -05:00
Jonathan Lamothe
d6ae171dc8 pad menu selections to same width 2020-01-22 21:10:21 -05:00
Jonathan Lamothe
72b6f05700 changed menu style
...to be closer to the original program's menu style
2020-01-22 20:59:09 -05:00
Jonathan Lamothe
4c7a756c5e updated change log 2020-01-22 16:30:19 -05:00
Jonathan Lamothe
ea3ca4e578 Merge pull request #65 from mtlstats/title-screen
Title screen
2020-01-22 13:58:12 -05:00
Jonathan Lamothe
179a864cfa added header to title page 2020-01-22 13:48:34 -05:00
Jonathan Lamothe
f2b2ff3fef centred title screen 2020-01-22 13:43:58 -05:00
Jonathan Lamothe
9c2e2291c8 draw box around title 2020-01-22 08:49:13 -05:00
Jonathan Lamothe
abad72ce01 built basic title screen 2020-01-22 00:39:06 -05:00
Jonathan Lamothe
a9d4d3351f implemented title screen controller 2020-01-21 22:55:22 -05:00
Jonathan Lamothe
45aea607b2 added title screen logic branch 2020-01-21 22:20:01 -05:00
Jonathan Lamothe
be9d7d80bb updated change log 2020-01-16 22:16:20 -05:00
Jonathan Lamothe
683c36e2b6 Merge pull request #64 from mtlstats/edit-standings
Edit standings
2020-01-16 21:52:41 -05:00
Jonathan Lamothe
83c408cea2 implemented editing prompts 2020-01-16 21:46:35 -05:00
Jonathan Lamothe
dcbd68cdda implemented editHomeStandingsC and editAwayStandingsC 2020-01-16 21:10:14 -05:00
Jonathan Lamothe
717f2d5932 made standings table prettier 2020-01-16 19:58:07 -05:00
Jonathan Lamothe
d5de834510 implemented edit functions in Mtlstats.Actions.EditState module 2020-01-16 19:47:12 -05:00
Jonathan Lamothe
75a47ca852 implemented Mtlstats.Menu.EditStandings.subMenu 2020-01-16 17:54:05 -05:00
Jonathan Lamothe
d4de7c6f8b implemented editHomeStandings and editAwayStandings 2020-01-16 17:43:53 -05:00
Jonathan Lamothe
d50d055b0b implemented editHomeStandings and editAwayStandings 2020-01-16 17:17:24 -05:00
Jonathan Lamothe
9c7c295a4b implemented editAwayStandings 2020-01-16 15:00:08 -05:00
Jonathan Lamothe
49963277be implemented editHomeStandings 2020-01-16 12:42:33 -05:00
Jonathan Lamothe
264d9f81e2 moved editStandings to Mtlstats.Actions.EditStandings module 2020-01-16 00:03:31 -05:00
Jonathan Lamothe
6a0d1f7203 implemented editStandingsMenu 2020-01-15 23:21:37 -05:00
Jonathan Lamothe
18683c1c6e implemented Mtlstats.Control.EditStandings.valsFor 2020-01-15 01:05:45 -05:00
Jonathan Lamothe
107ed507e2 implemented Mtlstats.Control.EditStandings.header 2020-01-15 01:00:26 -05:00
Jonathan Lamothe
baf040deea implemented editStandingsC 2020-01-15 00:43:48 -05:00
Jonathan Lamothe
c3bac5e624 created Mtlstats.Control.EditStandings module 2020-01-15 00:34:45 -05:00
Jonathan Lamothe
119cb873eb implemented editStandings 2020-01-15 00:26:46 -05:00
Jonathan Lamothe
82603ba504 added "Edit Standings" to edit menu 2020-01-15 00:15:58 -05:00
Jonathan Lamothe
a909b9ba7a added EditStandings mode 2020-01-15 00:08:04 -05:00
Jonathan Lamothe
802bf7314e fixed formatting of change log 2020-01-14 23:33:39 -05:00
Jonathan Lamothe
a3124aca58 Merge pull request #63 from mtlstats/save-db
save a copy of the database on new season
2020-01-14 03:29:52 -05:00
Jonathan Lamothe
f113e46564 updated change log 2020-01-14 03:23:39 -05:00
Jonathan Lamothe
39646f3fa7 save a copy of the database on start of new season 2020-01-14 03:21:40 -05:00
Jonathan Lamothe
2bf8d15bd4 logic branch for database saving on new season 2020-01-14 02:42:30 -05:00
Jonathan Lamothe
3009a8f60c Merge pull request #62 from mtlstats/clear-rookies
clear rookies on new (regular) season
2020-01-11 02:39:41 -05:00
Jonathan Lamothe
3b4ce50ae8 clear rookies on new (regular) season 2020-01-11 02:30:09 -05:00
Jonathan Lamothe
fcfbcea72f Merge pull request #61 from mtlstats/active-col
Added active field to players/goalies
2020-01-11 01:59:51 -05:00
Jonathan Lamothe
d132ebd502 updated change log 2020-01-11 01:52:40 -05:00
Jonathan Lamothe
75cd253f3f allow user to toggle active flag for Player/Goalie 2020-01-11 01:49:27 -05:00
Jonathan Lamothe
063bebfbb5 test active field in JSON for Player/Goalie 2020-01-11 01:49:27 -05:00
Jonathan Lamothe
7923827d22 flag inactive goalies in goalieName 2020-01-11 01:49:27 -05:00
Jonathan Lamothe
461fb5d942 mark inactive players in playerName 2020-01-11 01:27:01 -05:00
Jonathan Lamothe
e38275aefe added active field to Player and Goalie 2020-01-11 01:21:16 -05:00
Jonathan Lamothe
dd6f604cd7 version 0.10.0 2020-01-11 00:30:51 -05:00
Jonathan Lamothe
1763f142f1 updated copyright 2020-01-11 00:29:45 -05:00
Jonathan Lamothe
b442b6a360 updated change log 2020-01-09 01:55:57 -05:00
Jonathan Lamothe
119c28ef18 Merge pull request #60 from mtlstats/rookie-col
Add rookie flag to players/goalies
2020-01-09 01:54:48 -05:00
Jonathan Lamothe
b69b3fce7a fixed editSelectedGoalie test labels 2020-01-09 01:43:35 -05:00
Jonathan Lamothe
59026de077 use editSelectedGoalie for all goalie editing 2020-01-09 01:35:37 -05:00
Jonathan Lamothe
52f1e34d49 implemented rookie flag toggling for goalies 2020-01-09 01:31:24 -05:00
Jonathan Lamothe
2c561e9807 use editSelectedPlayer for all player edits 2020-01-09 01:23:20 -05:00
Jonathan Lamothe
e2aeb5bfa4 enable toggling of rookie flag for players 2020-01-09 01:23:20 -05:00
Jonathan Lamothe
5b40a5942b mark rookies in reports 2020-01-09 00:00:05 -05:00
Jonathan Lamothe
ee3cea5643 mark rookies in goalieSummary 2020-01-08 23:54:16 -05:00
Jonathan Lamothe
5209c4a296 mark rookies in playerSummary 2020-01-04 12:10:19 -05:00
Jonathan Lamothe
e077c32956 implemented goalieName 2020-01-04 11:02:58 -05:00
Jonathan Lamothe
8dcef502be implemented playerName 2020-01-03 22:01:09 -05:00
Jonathan Lamothe
3ee97406f1 make database less brittle when something's wrong with the JSON file 2020-01-03 21:37:33 -05:00
Jonathan Lamothe
2768934c7c added rookie field to Player and Goalie values 2020-01-03 21:33:39 -05:00
Jonathan Lamothe
6c8ec21ffe Merge pull request #59 from mtlstats/line-numbers
add line numbers to lifetime player/goalie reports
2020-01-02 23:57:52 -05:00
Jonathan Lamothe
ba3f8a5a6c updated change log 2020-01-02 23:51:36 -05:00
Jonathan Lamothe
3a71dc1e62 add line numbers to lifetime goalie report 2020-01-02 23:50:21 -05:00
Jonathan Lamothe
d14f2ba527 line numbers on lifetime player report 2020-01-02 23:43:44 -05:00
Jonathan Lamothe
acd45229e7 Merge pull request #58 from mtlstats/edit-shutouts
allow user to edit goalie shutouts
2020-01-02 00:48:05 -05:00
Jonathan Lamothe
aff1d5c255 allow user to edit goalie shutouts 2020-01-02 00:42:04 -05:00
Jonathan Lamothe
0448a4beee updated change log 2020-01-02 00:20:26 -05:00
Jonathan Lamothe
ddb9394ab7 Merge pull request #57 from mtlstats/batch-edit
Allow editing of player/goalie YTD/lifetime stats at once
2020-01-02 00:16:39 -05:00
Jonathan Lamothe
071aa3bd8e allow batch editing of goalie YTD/lifetime stats 2020-01-02 00:07:29 -05:00
Jonathan Lamothe
ac95601609 removed (redundant) Mtlstats.Actions.EditGoalie module 2020-01-01 23:40:24 -05:00
Jonathan Lamothe
9606436e9e don't edit goalie stats when no input entered 2020-01-01 23:36:37 -05:00
Jonathan Lamothe
30807b7e2e implemented batch editing of all player ytd/lifetime stats 2020-01-01 23:09:07 -05:00
Jonathan Lamothe
34b743a55b don't edit player values when no new value entered 2020-01-01 23:09:07 -05:00
Jonathan Lamothe
63bd9a6de4 implemented numPromptWithFallback 2019-12-31 22:44:37 -05:00
Jonathan Lamothe
1f3ff5912c Merge pull request #56 from mtlstats/edit-return
make player edit prompts return to the appropriate menus
2019-12-28 21:58:35 -05:00
Jonathan Lamothe
7bb0981ed6 make player edit prompts return to the appropriate menus 2019-12-28 21:53:23 -05:00
Jonathan Lamothe
eb4107365c Merge pull request #55 from mtlstats/fix-case
force player/goalie names to correct case when editing
2019-12-28 21:36:24 -05:00
Jonathan Lamothe
6238e39f69 force player/goalie names to correct case when editing 2019-12-28 21:31:42 -05:00
Jonathan Lamothe
1adf6de990 Merge pull request #54 from mtlstats/player-zero
don't show player zero in reports
2019-12-28 21:05:23 -05:00
Jonathan Lamothe
2840298467 don't show player zero in reports 2019-12-28 20:59:04 -05:00
59 changed files with 3401 additions and 2042 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,6 @@
.stack-work/
mtlstats.cabal
.vagrant
data
*.log
*~

View File

@@ -1,40 +0,0 @@
# This is the simple Travis configuration, which is intended for use
# on applications which do not require cross-platform and
# multiple-GHC-version support. For more information and other
# options, see:
#
# https://docs.haskellstack.org/en/stable/travis_ci/
#
# Copy these contents into the root directory of your Github project in a file
# named .travis.yml
# Choose a build environment
dist: xenial
# Do not choose a language; we provide our own build tools.
language: generic
# Caching so the next build will be fast too.
cache:
directories:
- $HOME/.stack
# Ensure necessary system libraries are present
addons:
apt:
packages:
- libgmp-dev
before_install:
# Download and unpack the stack executable
- mkdir -p ~/.local/bin
- export PATH=$HOME/.local/bin:$PATH
- travis_retry curl -L https://get.haskellstack.org/stable/linux-x86_64.tar.gz | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack'
install:
# Build dependencies
- stack --no-terminal --install-ghc test --only-dependencies
script:
# Build the package, its tests, and its docs and run the tests
- stack --no-terminal test --haddock --no-haddock-deps

View File

@@ -1,5 +1,61 @@
# Changelog for mtlstats
## 0.17.0.1
- fixed autocapitalization
## 0.17.0
- updated code to use brick instead of ncurses
## 0.16.1
- Don't automatically start a new game on new season
## 0.16.0
- enter months by number
## 0.15.2
- allow ties
## 0.15.1
- only search for active players/goalies on game data input
## 0.15.0
- Ask for database to load on start-up
- Add page break to report file
- Implemented player/goalie deletion
## 0.14.0
- Fixed a bug that was causing shutouts to not be recorded
- Output report to a text file (report.txt)
## 0.13.0
- Added autocomplete to player position prompt
- Don't prompt for lifetime stats on rookie player/goalie creation
- Ask whether a player/goalie is active on creation
- Don't ask which goalie to assign the game to when there's only one
## 0.12.0
- Edit lifetime stats on new player/goalie creation
- Sort goalies by minutes played
- Subsort players by lifetime points
- Changed wording on edit menus
## 0.11.0
- Added active flag to players/goalies
- Clear rookie flag on new (regular) season
- Save a copy of the database on new season
- Implemented game standings editing
- Added title screen
- Changed sytling of menus
## 0.10.0
- Don't show player number zero in reports
- Fixed player/goalie name capitalisation on edit
- Return to correct edit menus after editing player stats
- Enabled batch editing of player/goalie YTD/lifetime stats
- Bugfix: allow user to edit goalie shutouts
- Added line numbers to lifetime player/goalie reports
- Implemented rookie flag
## 0.9.0
- Bugfix: Display lifetime stats in report, not YTD
- Force expected capitalization on player/goalie names
@@ -24,24 +80,20 @@
- Reset game standings on new season
## 0.5.0
- Fixed player creation bug
- Prompt for goalie informaiton on game data entry
- Implemented player editing
## 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
- Overtime losses don't count in the loss column
- Confirm game data with user before updating stats
- Implemented player creation

View File

@@ -1,7 +1,7 @@
{-
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -21,10 +21,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
module Main where
import Control.Monad.Trans.State (evalStateT)
import UI.NCurses (runCurses)
import Brick.Main (defaultMain)
import Control.Monad (void)
import Mtlstats
import Mtlstats.Types
main :: IO ()
main = runCurses $ initState >>= evalStateT mainLoop
main = void $ defaultMain app newProgState

View File

@@ -1,10 +1,9 @@
name: mtlstats
version: 0.9.0
github: "mtlstats/mtlstats"
license: GPL-3
version: 0.17.0.1
license: GPL-3.0-or-later
author: "Jonathan Lamothe"
maintainer: "jlamothe1980@gmail.com"
copyright: "Rhéal Lamothe"
copyright: "1984, 1985, 2019-2021, 2023 Rhéal Lamothe"
extra-source-files:
- README.md
@@ -21,17 +20,20 @@ description: Please see the README on GitHub at <https://github.com/mtls
dependencies:
- base >= 4.7 && < 5
- aeson >= 1.4.4.0 && < 1.5
- aeson >= 2.0.3.0 && < 2.1
- bytestring >= 0.11.4.0 && < 0.12
- brick >= 1.4 && < 1.5
- containers >= 0.6.0.1 && < 0.7
- easy-file >= 0.2.2 && < 0.3
- extra >= 1.6.17 && < 1.7
- extra >= 1.7.13 && < 1.8
- microlens >= 0.4.12.0 && < 0.5
- microlens-mtl >= 0.2.0.3 && < 0.3
- microlens-th >= 0.4.2.3 && < 0.5
- ncurses >= 0.2.16 && < 0.3
- random >= 1.1 && < 1.2
- time >= 1.8.0.2 && < 1.9
- transformers >= 0.5.6.2 && < 0.6
- bytestring
- microlens
- mtl >= 2.2.2 && < 2.3
- random >= 1.2.1.1 && < 1.3
- text-zipper >= 0.12 && < 0.13
- time >= 1.11.1.1 && < 1.12
- vty >= 5.37 && < 5.38
ghc-options:
- -Wall
@@ -61,5 +63,5 @@ tests:
- -with-rtsopts=-N
dependencies:
- mtlstats
- hspec >= 2.7.1 && < 2.8
- hspec >= 2.9.7 && < 2.10
- unordered-containers

View File

@@ -1,7 +1,7 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -19,56 +19,45 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
-}
{-# LANGUAGE ScopedTypeVariables #-}
module Mtlstats (app) where
module Mtlstats (initState, mainLoop) where
import Brick.AttrMap (AttrMap, forceAttrMap)
import Brick.Main (App (..), halt, showFirstCursor)
import Brick.Types (BrickEvent (VtyEvent), Widget)
import Brick.Util (on)
import Brick.Widgets.Core (fill)
import Control.Monad.State.Class (gets)
import Graphics.Vty.Attributes.Color (blue, white)
import Graphics.Vty.Input.Events
( Event (EvKey)
, Modifier (MCtrl)
, Key (KChar)
)
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.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
-- | Initializes the progran
initState :: C.Curses ProgState
initState = do
C.setEcho False
void $ C.setCursorMode C.CursorInvisible
db <- liftIO $ do
dir <- getAppUserDataDirectory appName
let dbFile = dir </> dbFname
fromMaybe newDatabase <$> catch
(decodeFileStrict dbFile)
(\(_ :: IOException) -> return Nothing)
return
$ newProgState
& database .~ db
-- | The main application
app :: App ProgState () ()
app = App
{ appDraw = draw
, appChooseCursor = showFirstCursor
, appHandleEvent = handler
, appStartEvent = return ()
, appAttrMap = const myAttrMap
}
-- | Main program loop
mainLoop :: Action ()
mainLoop = do
draw :: ProgState -> [Widget ()]
draw s =
[ drawController (dispatch s) s
, fill ' '
]
handler :: Handler ()
handler (VtyEvent (EvKey (KChar 'c') [MCtrl])) = halt
handler e = do
c <- gets dispatch
get >>= lift . draw . drawController c
w <- lift C.defaultWindow
whenM (lift (fromJust <$> C.getEvent w Nothing) >>= handleController c)
mainLoop
handleController c e
draw :: C.Update C.CursorMode -> C.Curses ()
draw u = do
void $ C.setCursorMode C.CursorInvisible
w <- C.defaultWindow
cm <- C.updateWindow w $ do
C.clear
u
C.render
void $ C.setCursorMode cm
myAttrMap :: AttrMap
myAttrMap = forceAttrMap (white `on` blue)

View File

@@ -1,7 +1,7 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -19,38 +19,56 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Mtlstats.Actions
( startNewSeason
, resetYtd
, clearRookies
, resetStandings
, startNewGame
, addChar
, removeChar
, createPlayer
, createGoalie
, edit
, editPlayer
, editSelectedPlayer
, editGoalie
, editSelectedGoalie
, addPlayer
, addGoalie
, resetCreatePlayerState
, resetCreateGoalieState
, backHome
, scrollUp
, scrollDown
, clearEditor
, loadDatabase
, saveDatabase
) where
import Control.Monad.Trans.State (modify)
import Brick.Main (viewportScroll)
import Brick.Widgets.Edit (Editor, applyEdit)
import Control.Exception (IOException, catch)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.State.Class (modify)
import Data.Aeson (decodeFileStrict, encodeFile)
import Data.Maybe (fromMaybe)
import Data.Text.Zipper (gotoBOF, killToEOF)
import Lens.Micro ((^.), (&), (.~), (%~))
import Lens.Micro.Mtl ((.=), use)
import System.EasyFile
( createDirectoryIfMissing
, getAppUserDataDirectory
, (</>)
)
import Mtlstats.Config
import Mtlstats.Types
import Mtlstats.Util
-- | Starts a new season
startNewSeason :: ProgState -> ProgState
startNewSeason = (progMode .~ NewSeason) . (database . dbGames .~ 0)
startNewSeason
= (progMode .~ NewSeason False)
. (database.dbGames .~ 0)
-- | Resets all players year-to-date stats
resetYtd :: ProgState -> ProgState
@@ -58,6 +76,12 @@ resetYtd
= (database . dbPlayers %~ map (pYtd .~ newPlayerStats))
. (database . dbGoalies %~ map (gYtd .~ newGoalieStats))
-- | Clears the rookie flag from all players/goalies
clearRookies :: ProgState -> ProgState
clearRookies = database
%~ (dbPlayers %~ map (pRookie .~ False))
. (dbGoalies %~ map (gRookie .~ False))
-- | Resets game standings
resetStandings :: ProgState -> ProgState
resetStandings = database
@@ -70,16 +94,6 @@ startNewGame
= (progMode .~ NewGame newGameState)
. (database . dbGames %~ succ)
-- | Adds a character to the input buffer
addChar :: Char -> ProgState -> ProgState
addChar c = inputBuffer %~ (++[c])
-- | Removes a character from the input buffer (if possible)
removeChar :: ProgState -> ProgState
removeChar = inputBuffer %~ \case
"" -> ""
str -> init str
-- | Starts player creation mode
createPlayer :: ProgState -> ProgState
createPlayer = let
@@ -106,19 +120,49 @@ edit = progMode .~ EditMenu
editPlayer :: ProgState -> ProgState
editPlayer = progMode .~ EditPlayer newEditPlayerState
-- | Edits the selected 'Player'
editSelectedPlayer
:: (Player -> Player)
-- ^ The modification to be made to the 'Player'
-> ProgState
-> ProgState
editSelectedPlayer f s = fromMaybe s $ do
n <- s^.progMode.editPlayerStateL.epsSelectedPlayer
let
players = s^.database.dbPlayers
players' = modifyNth n f players
Just $ s & database.dbPlayers .~ players'
-- | Starts the 'Goalie' editing process
editGoalie :: ProgState -> ProgState
editGoalie = progMode .~ EditGoalie newEditGoalieState
-- | Edits the selected 'Goalie'
editSelectedGoalie
:: (Goalie -> Goalie)
-- ^ The modification to be made to the 'Goalie'
-> ProgState
-> ProgState
editSelectedGoalie f s = fromMaybe s $ do
n <- s^.progMode.editGoalieStateL.egsSelectedGoalie
let
goalies = s^.database.dbGoalies
goalies' = modifyNth n f goalies
Just $ s & database.dbGoalies .~ goalies'
-- | Adds the entered player to the roster
addPlayer :: ProgState -> ProgState
addPlayer s = fromMaybe s $ do
let cps = s^.progMode.createPlayerStateL
num <- cps^.cpsNumber
num <- cps^.cpsNumber
rFlag <- cps^.cpsRookieFlag
aFlag <- cps^.cpsActiveFlag
let
name = cps^.cpsName
pos = cps^.cpsPosition
player = newPlayer num name pos
& pRookie .~ rFlag
& pActive .~ aFlag
Just $ s & database.dbPlayers
%~ (++[player])
@@ -126,10 +170,14 @@ addPlayer s = fromMaybe s $ do
addGoalie :: ProgState -> ProgState
addGoalie s = fromMaybe s $ do
let cgs = s^.progMode.createGoalieStateL
num <- cgs^.cgsNumber
num <- cgs^.cgsNumber
rFlag <- cgs^.cgsRookieFlag
aFlag <- cgs^.cgsActiveFlag
let
name = cgs^.cgsName
goalie = newGoalie num name
& gRookie .~ rFlag
& gActive .~ aFlag
Just $ s & database.dbGoalies
%~ (++[goalie])
@@ -149,14 +197,35 @@ resetCreateGoalieState = progMode.createGoalieStateL
-- | Resets the program state back to the main menu
backHome :: ProgState -> ProgState
backHome
= (progMode .~ MainMenu)
. (inputBuffer .~ "")
. (scrollOffset .~ 0)
= (progMode .~ MainMenu)
. (editorW %~ clearEditor)
. (scroller .~ viewportScroll ())
-- | Scrolls the display up
scrollUp :: ProgState -> ProgState
scrollUp = scrollOffset %~ max 0 . pred
-- | Clears an editor
clearEditor :: Editor String () -> Editor String ()
clearEditor = applyEdit $ killToEOF . gotoBOF
-- | Scrolls the display down
scrollDown :: ProgState -> ProgState
scrollDown = scrollOffset %~ succ
-- | Loads the database
loadDatabase :: Action ()
loadDatabase = do
dbFile <- dbSetup
liftIO
(catch
(decodeFileStrict dbFile)
(\(_ :: IOException) -> return Nothing))
>>= mapM_ (database .=)
-- | Saves the database
saveDatabase :: Action ()
saveDatabase = do
db <- use database
dbFile <- dbSetup
liftIO $ encodeFile dbFile db
dbSetup :: Action String
dbSetup = do
fn <- use dbName
liftIO $ do
dir <- getAppUserDataDirectory appName
createDirectoryIfMissing True dir
return $ dir </> fn ++ ".json"

View File

@@ -1,164 +0,0 @@
{- |
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.Actions.EditGoalie
( editGoalieNumber
, editGoalieName
, editGoalieYtdGames
, editGoalieYtdMins
, editGoalieYtdGoals
, editGoalieYtdWins
, editGoalieYtdLosses
, editGoalieYtdTies
, editGoalieLtGames
, editGoalieLtMins
, editGoalieLtGoals
, editGoalieLtWins
, editGoalieLtLosses
, editGoalieLtTies
) where
import Control.Monad (void)
import Data.Maybe (fromMaybe)
import Lens.Micro ((^.), (&), (.~), (%~))
import Mtlstats.Types
import Mtlstats.Util
-- | Edits a goalie's number
editGoalieNumber
:: Int
-- ^ New goalie number
-> ProgState
-> ProgState
editGoalieNumber num = editGoalie (gNumber .~ num) EGMenu
-- | Edits a goalie's name
editGoalieName
:: String
-- ^ The new name
-> ProgState
-> ProgState
editGoalieName name = editGoalie (gName .~ name) EGMenu
-- | Edits a goalie's YTD games
editGoalieYtdGames
:: Int
-- ^ The number of games played
-> ProgState
-> ProgState
editGoalieYtdGames games = editGoalie (gYtd.gsGames .~ games) EGYtd
-- | Edits a goalie's YTD minutes
editGoalieYtdMins
:: Int
-- ^ The number of minutes played
-> ProgState
-> ProgState
editGoalieYtdMins mins = editGoalie (gYtd.gsMinsPlayed .~ mins) EGYtd
-- | Edits a goalie's YTD goals allowed
editGoalieYtdGoals
:: Int
-- ^ The number of goals
-> ProgState
-> ProgState
editGoalieYtdGoals goals = editGoalie (gYtd.gsGoalsAllowed .~ goals) EGYtd
-- | Edits a goalie's YTD wins
editGoalieYtdWins
:: Int
-- ^ The number of wins
-> ProgState
-> ProgState
editGoalieYtdWins wins = editGoalie (gYtd.gsWins .~ wins) EGYtd
-- | Edits a goalie's YTD losses
editGoalieYtdLosses
:: Int
-- ^ The number of losses
-> ProgState
-> ProgState
editGoalieYtdLosses losses = editGoalie (gYtd.gsLosses .~ losses) EGYtd
-- | Edits a goalie's YTD ties
editGoalieYtdTies
:: Int
-- ^ The number of ties
-> ProgState
-> ProgState
editGoalieYtdTies ties = editGoalie (gYtd.gsTies .~ ties) EGYtd
-- | Edits a goalie's lifetime games played
editGoalieLtGames
:: Int
-- ^ The number of games
-> ProgState
-> ProgState
editGoalieLtGames games = editGoalie (gLifetime.gsGames .~ games) EGLifetime
-- | Edits a goalie's lifetime minutes played
editGoalieLtMins
:: Int
-- ^ The number of minutes
-> ProgState
-> ProgState
editGoalieLtMins mins = editGoalie (gLifetime.gsMinsPlayed .~ mins) EGLifetime
-- | Edits a goalie's lifetime goals allowed
editGoalieLtGoals
:: Int
-- ^ The number of goals
-> ProgState
-> ProgState
editGoalieLtGoals goals = editGoalie (gLifetime.gsGoalsAllowed .~ goals) EGLifetime
-- | Edits a goalie's lifetime wins
editGoalieLtWins
:: Int
-- ^ The number of wins
-> ProgState
-> ProgState
editGoalieLtWins wins = editGoalie (gLifetime.gsWins .~ wins) EGLifetime
-- | Edits a goalie's lifetime losses
editGoalieLtLosses
:: Int
-- ^ The number of losses
-> ProgState
-> ProgState
editGoalieLtLosses losses = editGoalie (gLifetime.gsLosses .~ losses) EGLifetime
-- | Edits a goalie's lifetime ties
editGoalieLtTies
:: Int
-- ^ The number of ties
-> ProgState
-> ProgState
editGoalieLtTies ties = editGoalie (gLifetime.gsTies .~ ties) EGLifetime
editGoalie :: (Goalie -> Goalie) -> EditGoalieMode -> ProgState -> ProgState
editGoalie f mode s = fromMaybe s $ do
gid <- s^.progMode.editGoalieStateL.egsSelectedGoalie
void $ nth gid $ s^.database.dbGoalies
Just $ s
& database.dbGoalies %~ modifyNth gid f
& progMode.editGoalieStateL.egsMode .~ mode

View File

@@ -0,0 +1,70 @@
{- |
mtlstats
Copyright (C) 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.Actions.EditStandings
( editStandings
, editHomeStandings
, editAwayStandings
, editWins
, editLosses
, editOvertime
, editGoalsFor
, editGoalsAgainst
) where
import Lens.Micro ((.~))
import Mtlstats.Types
-- | Enters edit standings mode
editStandings :: ProgState -> ProgState
editStandings = progMode .~ EditStandings ESMMenu
-- | Edits the home standings
editHomeStandings :: ProgState -> ProgState
editHomeStandings = progMode .~ EditStandings (ESMHome ESMSubMenu)
-- | Edits the road standings
editAwayStandings :: ProgState -> ProgState
editAwayStandings = progMode .~ EditStandings (ESMAway ESMSubMenu)
-- | Changes to edit wins mode
editWins :: ProgState -> ProgState
editWins = doEdit ESMEditWins
-- | Changes to edit losses mode
editLosses :: ProgState -> ProgState
editLosses = doEdit ESMEditLosses
-- | Changes to edit overtime mode
editOvertime :: ProgState -> ProgState
editOvertime = doEdit ESMEditOvertime
-- | Changes to edit goals for mode
editGoalsFor :: ProgState -> ProgState
editGoalsFor = doEdit ESMEditGoalsFor
-- | Changes to edit goals against mode
editGoalsAgainst :: ProgState -> ProgState
editGoalsAgainst = doEdit ESMEditGoalsAgainst
doEdit :: ESMSubMode -> ProgState -> ProgState
doEdit = (progMode.editStandingsModeL.esmSubModeL .~)

View File

@@ -1,7 +1,7 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -43,9 +43,7 @@ import Mtlstats.Util
overtimeCheck :: ProgState -> ProgState
overtimeCheck s
| fromMaybe False $ gameTied $ s^.progMode.gameStateL =
s & progMode.gameStateL
%~ (homeScore .~ Nothing)
. (awayScore .~ Nothing)
s & progMode.gameStateL.overtimeFlag ?~ True
| fromMaybe False $ gameWon $ s^.progMode.gameStateL =
s & progMode.gameStateL.overtimeFlag ?~ False
| otherwise = s
@@ -124,12 +122,13 @@ awardGoal n ps = ps
(\m -> let
stats = M.findWithDefault newPlayerStats n m
in M.insert n (stats & psGoals %~ succ) m)
& database.dbPlayers %~ map
(\(i, p) -> if i == n
& database.dbPlayers %~ zipWith
(\i p -> if i == n
then p
& pYtd.psGoals %~ succ
& pLifetime.psGoals %~ succ
else p) . zip [0..]
else p)
[0..]
-- | Awards an assist to a player
awardAssist
@@ -142,12 +141,13 @@ awardAssist n ps = ps
(\m -> let
stats = M.findWithDefault newPlayerStats n m
in M.insert n (stats & psAssists %~ succ) m)
& database.dbPlayers %~ map
(\(i, p) -> if i == n
& database.dbPlayers %~ zipWith
(\i p -> if i == n
then p
& pYtd.psAssists %~ succ
& pLifetime.psAssists %~ succ
else p) . zip [0..]
else p)
[0..]
-- | Resets the entered data for the current goal
resetGoalData :: ProgState -> ProgState

View File

@@ -1,7 +1,7 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -36,8 +36,12 @@ import Mtlstats.Util
-- | Attempts to finish game goalie entry
finishGoalieEntry :: ProgState -> ProgState
finishGoalieEntry s = s & progMode.gameStateL.gameGoaliesRecorded
.~ not (null $ s^.progMode.gameStateL.gameGoalieStats)
finishGoalieEntry s = case M.toList $ s^.progMode.gameStateL.gameGoalieStats of
[] -> s
[(gid, _)] -> setGameGoalie gid s'
_ -> s'
where
s' = s & progMode.gameStateL.gameGoaliesRecorded .~ True
-- | Records the goalie's game stats
recordGoalieStats :: ProgState -> ProgState
@@ -83,18 +87,22 @@ setGameGoalie
-> ProgState
setGameGoalie gid s = fromMaybe s $ do
let gs = s^.progMode.gameStateL
won <- gameWon gs
lost <- gameLost gs
tied <- gs^.overtimeFlag
won <- gameWon gs
lost <- gameLost gs
tied <- gs^.overtimeFlag
shutout <- (==0) <$> otherScore gs
let
w = if won then 1 else 0
l = if lost then 1 else 0
t = if tied then 1 else 0
w = if won then 1 else 0
l = if lost then 1 else 0
t = if tied then 1 else 0
so = if shutout then 1 else 0
updateStats
= (gsWins +~ w)
. (gsLosses +~ l)
. (gsTies +~ t)
= (gsWins +~ w)
. (gsLosses +~ l)
. (gsTies +~ t)
. (gsShutouts +~ so)
updateGoalie
= (gYtd %~ updateStats)

View File

@@ -1,7 +1,7 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -33,10 +33,6 @@ maxFunKeys = 9
appName :: String
appName = "mtlstats"
-- | The database filename
dbFname :: String
dbFname = "database.json"
-- | The maximum number of assists
maxAssists :: Int
maxAssists = 2
@@ -44,3 +40,11 @@ maxAssists = 2
-- | The length of a typical game (in minutes)
gameLength :: Int
gameLength = 60
-- | Report output filename
reportFilename :: FilePath
reportFilename = "report.txt"
-- | Number of columns in report file
reportCols :: Int
reportCols = 79

View File

@@ -1,7 +1,7 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -21,18 +21,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
module Mtlstats.Control (dispatch) where
import Control.Monad (join)
import Control.Monad.Trans.State (gets, modify)
import Data.Maybe (fromJust)
import Lens.Micro ((^.))
import Lens.Micro.Extras (view)
import qualified UI.NCurses as C
import Mtlstats.Actions
import Mtlstats.Control.CreateGoalie
import Mtlstats.Control.CreatePlayer
import Mtlstats.Control.EditGoalie
import Mtlstats.Control.EditPlayer
import Mtlstats.Control.EditStandings
import Mtlstats.Control.NewGame
import Mtlstats.Handlers
import Mtlstats.Control.TitleScreen
import Mtlstats.Menu
import Mtlstats.Prompt
import Mtlstats.Types
@@ -41,117 +38,28 @@ import Mtlstats.Types
-- run
dispatch :: ProgState -> Controller
dispatch s = case s^.progMode of
MainMenu -> mainMenuC
NewSeason -> newSeasonC
NewGame gs -> newGameC gs
EditMenu -> editMenuC
CreatePlayer cps
| null $ cps^.cpsNumber -> getPlayerNumC
| null $ cps^.cpsName -> getPlayerNameC
| null $ cps^.cpsPosition -> getPlayerPosC
| otherwise -> confirmCreatePlayerC
CreateGoalie cgs
| null $ cgs^.cgsNumber -> getGoalieNumC
| null $ cgs^.cgsName -> getGoalieNameC
| otherwise -> confirmCreateGoalieC
EditPlayer eps -> editPlayerC eps
EditGoalie egs -> editGoalieC egs
TitleScreen -> titleScreenC
MainMenu -> mainMenuC s
NewSeason flag -> newSeasonC flag
NewGame gs -> newGameC gs
EditMenu -> editMenuC
CreatePlayer cps -> createPlayerC cps
CreateGoalie cgs -> createGoalieC cgs
EditPlayer eps -> editPlayerC eps
EditGoalie egs -> editGoalieC egs
(EditStandings esm) -> editStandingsC esm
mainMenuC :: Controller
mainMenuC = Controller
{ drawController = const $ drawMenu mainMenu
, handleController = menuHandler mainMenu
}
mainMenuC :: ProgState -> Controller
mainMenuC s = if null $ s^.dbName
then promptController getDBPrompt
else Controller
{ drawController = const $ drawMenu mainMenu
, handleController = menuHandler mainMenu
}
newSeasonC :: Controller
newSeasonC = Controller
{ drawController = const $ drawMenu newSeasonMenu
, handleController = \e -> do
menuHandler newSeasonMenu e
return True
}
newSeasonC :: Bool -> Controller
newSeasonC False = promptController newSeasonPrompt
newSeasonC True = menuController newSeasonMenu
editMenuC :: Controller
editMenuC = menuController editMenu
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
}
getGoalieNumC :: Controller
getGoalieNumC = Controller
{ drawController = drawPrompt goalieNumPrompt
, handleController = \e -> do
promptHandler goalieNumPrompt e
return True
}
getGoalieNameC :: Controller
getGoalieNameC = Controller
{ drawController = drawPrompt goalieNamePrompt
, handleController = \e -> do
promptHandler goalieNamePrompt e
return True
}
confirmCreateGoalieC :: Controller
confirmCreateGoalieC = Controller
{ drawController = \s -> do
let cgs = s^.progMode.createGoalieStateL
C.drawString $ unlines
[ "Goalie number: " ++ show (fromJust $ cgs^.cgsNumber)
, " Goalie name: " ++ cgs^.cgsName
, ""
, "Create goalie: are you sure? (Y/N)"
]
return C.CursorInvisible
, handleController = \e -> do
case ynHandler e of
Just True -> do
modify addGoalie
join $ gets (^.progMode.createGoalieStateL.cgsSuccessCallback)
Just False ->
join $ gets (^.progMode.createGoalieStateL.cgsFailureCallback)
Nothing -> return ()
return True
}

View File

@@ -0,0 +1,102 @@
{- |
mtlstats
Copyright (C) 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.Control.CreateGoalie (createGoalieC) where
import Brick.Widgets.Core (str)
import Control.Monad.State.Class (gets, modify)
import Lens.Micro ((^.), (.~), (?~), (%~), to)
import Lens.Micro.Mtl ((.=))
import Mtlstats.Actions
import Mtlstats.Format
import Mtlstats.Handlers
import Mtlstats.Prompt
import Mtlstats.Types
import Mtlstats.Util
-- | Handles goalie creation
createGoalieC :: CreateGoalieState -> Controller
createGoalieC cgs
| null $ cgs^.cgsNumber = getGoalieNumC
| null $ cgs^.cgsName = getGoalieNameC
| null $ cgs^.cgsRookieFlag = getRookieFlagC
| null $ cgs^.cgsActiveFlag = getActiveFlagC
| otherwise = confirmCreateGoalieC
getGoalieNumC :: Controller
getGoalieNumC = promptController goalieNumPrompt
getGoalieNameC :: Controller
getGoalieNameC = promptController goalieNamePrompt
getRookieFlagC :: Controller
getRookieFlagC = Controller
{ drawController = const $
str "Is this goalie a rookie? (Y/N)"
, handleController = \e ->
modify $ case ynHandler e of
Just True -> progMode.createGoalieStateL
%~ (cgsRookieFlag ?~ True)
. (cgsActiveFlag ?~ True)
rf -> progMode.createGoalieStateL.cgsRookieFlag .~ rf
}
getActiveFlagC :: Controller
getActiveFlagC = Controller
{ drawController = const $ str "Is this goalie active? (Y/N)"
, handleController = \e ->
progMode.createGoalieStateL.cgsActiveFlag .= ynHandler e
}
confirmCreateGoalieC :: Controller
confirmCreateGoalieC = Controller
{ drawController = \s -> let
cgs = s^.progMode.createGoalieStateL
in linesToWidget
$ labelTable
[ ( "Goalie number", maybe "?" show $ cgs^.cgsNumber )
, ( "Goalie name", cgs^.cgsName )
, ( "Rookie", maybe "?" show $ cgs^.cgsRookieFlag )
, ( "Active", maybe "?" show $ cgs^.cgsActiveFlag )
]
++ [ ""
, "Create goalie: are you sure? (Y/N)"
]
, handleController = \e -> do
cgs <- gets (^.progMode.createGoalieStateL)
let
success = cgs^.cgsSuccessCallback
failure = cgs^.cgsFailureCallback
case ynHandler e of
Just True -> do
gid <- gets (^.database.dbGoalies.to length)
let rookie = cgs^.cgsRookieFlag == Just True
modify addGoalie
if rookie
then success
else modify $ progMode.editGoalieStateL
%~ (egsSelectedGoalie ?~ gid)
. (egsMode .~ EGLtGames True)
. (egsCallback .~ success)
Just False -> failure
Nothing -> return ()
}

View File

@@ -0,0 +1,105 @@
{- |
mtlstats
Copyright (C) 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.Control.CreatePlayer (createPlayerC) where
import Brick.Widgets.Core (str)
import Control.Monad.State.Class (gets, modify)
import Lens.Micro ((^.), (.~), (?~), (%~), to)
import Lens.Micro.Mtl ((.=), use)
import Mtlstats.Actions
import Mtlstats.Format
import Mtlstats.Handlers
import Mtlstats.Prompt
import Mtlstats.Types
import Mtlstats.Util
-- | Handles player creation
createPlayerC :: CreatePlayerState -> Controller
createPlayerC cps
| null $ cps^.cpsNumber = getPlayerNumC
| null $ cps^.cpsName = getPlayerNameC
| null $ cps^.cpsPosition = getPlayerPosC
| null $ cps^.cpsRookieFlag = getRookieFlagC
| null $ cps^.cpsActiveFlag = getActiveFlagC
| otherwise = confirmCreatePlayerC
getPlayerNumC :: Controller
getPlayerNumC = promptController playerNumPrompt
getPlayerNameC :: Controller
getPlayerNameC = promptController playerNamePrompt
getPlayerPosC :: Controller
getPlayerPosC = promptController playerPosPrompt
getRookieFlagC :: Controller
getRookieFlagC = Controller
{ drawController = const $ str "Is this player a rookie? (Y/N)"
, handleController = \e ->
modify $ case ynHandler e of
Just True -> progMode.createPlayerStateL
%~ (cpsRookieFlag ?~ True)
. (cpsActiveFlag ?~ True)
rf -> progMode.createPlayerStateL.cpsRookieFlag .~ rf
}
getActiveFlagC :: Controller
getActiveFlagC = Controller
{ drawController = const $ str "Is the player active? (Y/N)"
, handleController = \e ->
progMode.createPlayerStateL.cpsActiveFlag .= ynHandler e
}
confirmCreatePlayerC :: Controller
confirmCreatePlayerC = Controller
{ drawController = \s -> let cps = s^.progMode.createPlayerStateL
in linesToWidget
$ labelTable
[ ( "Player number", maybe "?" show $ cps^.cpsNumber )
, ( "Player name", cps^.cpsName )
, ( "Player position", cps^.cpsPosition )
, ( "Rookie", maybe "?" show $ cps^.cpsRookieFlag )
, ( "Active", maybe "?" show $ cps^.cpsActiveFlag )
]
++ [ ""
, "Create player: are you sure? (Y/N)"
]
, handleController = \e -> do
cps <- use $ progMode.createPlayerStateL
let
success = cps^.cpsSuccessCallback
failure = cps^.cpsFailureCallback
case ynHandler e of
Just True -> do
pid <- gets (^.database.dbPlayers.to length)
let rookie = cps^.cpsRookieFlag == Just True
modify addPlayer
if rookie
then success
else modify $ progMode.editPlayerStateL
%~ (epsSelectedPlayer ?~ pid)
. (epsMode .~ EPLtGoals True)
. (epsCallback .~ success)
Just False -> failure
Nothing -> return ()
}

View File

@@ -1,7 +1,7 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -23,10 +23,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
module Mtlstats.Control.EditGoalie (editGoalieC) where
import Brick.Types (Widget)
import Brick.Widgets.Core (str, vBox)
import Control.Monad.State.Class (modify)
import Data.Maybe (fromMaybe)
import Lens.Micro ((^.))
import UI.NCurses as C
import Lens.Micro.Mtl ((.=), (%=), use)
import Mtlstats.Actions
import Mtlstats.Handlers
import Mtlstats.Helpers.Goalie
import Mtlstats.Menu
import Mtlstats.Menu.EditGoalie
@@ -39,84 +44,128 @@ import Mtlstats.Util
editGoalieC :: EditGoalieState -> Controller
editGoalieC egs
| null $ egs^.egsSelectedGoalie = selectC
| otherwise = editC $ egs^.egsMode
| otherwise = editC (egs^.egsCallback) (egs^.egsMode)
selectC :: Controller
selectC = promptController goalieToEditPrompt
editC :: EditGoalieMode -> Controller
editC = \case
EGMenu -> menuC
EGNumber -> numberC
EGName -> nameC
EGYtd -> ytdMenuC
EGLifetime -> lifetimeMenuC
EGYtdGames -> ytdGamesC
EGYtdMins -> ytdMinsC
EGYtdGoals -> ytdGoalsC
EGYtdWins -> ytdWinsC
EGYtdLosses -> ytdLossesC
EGYtdTies -> ytdTiesC
EGLtGames -> ltGamesC
EGLtMins -> ltMinsC
EGLtGoals -> ltGoalsC
EGLtWins -> ltWinsC
EGLtLosses -> ltLossesC
EGLtTies -> ltTiesC
editC :: Action () -> EditGoalieMode -> Controller
editC cb =
( \case
EGMenu -> menuC
EGNumber -> numberC
EGName -> nameC
EGYtd -> ytdMenuC
EGLifetime -> lifetimeMenuC
EGDelete -> deleteC
EGYtdGames b -> ytdGamesC b
EGYtdMins b -> ytdMinsC b
EGYtdGoals b -> ytdGoalsC b
EGYtdShutouts b -> ytdShutoutsC b
EGYtdWins b -> ytdWinsC b
EGYtdLosses b -> ytdLossesC b
EGYtdTies -> ytdTiesC
EGLtGames b -> ltGamesC b
EGLtMins b -> ltMinsC b
EGLtGoals b -> ltGoalsC b
EGLtShutouts b -> ltShutoutsC b
EGLtWins b -> ltWinsC b
EGLtLosses b -> ltLossesC b
EGLtTies -> ltTiesC
) <*> return cb
menuC :: Controller
menuC = menuControllerWith header editGoalieMenu
menuC :: Action () -> Controller
menuC _ = menuControllerWith header editGoalieMenu
numberC :: Controller
numberC = promptController editGoalieNumberPrompt
numberC :: Action () -> Controller
numberC = promptController . editGoalieNumberPrompt
nameC :: Controller
nameC = promptController editGoalieNamePrompt
nameC :: Action () -> Controller
nameC = promptController . editGoalieNamePrompt
ytdMenuC :: Controller
ytdMenuC = menuControllerWith header editGoalieYtdMenu
ytdMenuC :: Action () -> Controller
ytdMenuC _ = menuControllerWith header editGoalieYtdMenu
lifetimeMenuC :: Controller
lifetimeMenuC = menuControllerWith header editGoalieLtMenu
lifetimeMenuC :: Action () -> Controller
lifetimeMenuC _ = menuControllerWith header editGoalieLtMenu
ytdGamesC :: Controller
ytdGamesC = promptController editGoalieYtdGamesPrompt
deleteC :: Action () -> Controller
deleteC _ = Controller
ytdMinsC :: Controller
ytdMinsC = promptController editGoalieYtdMinsPrompt
{ drawController = \s -> let
hdr = fromMaybe "" $ do
gid <- s^.progMode.editGoalieStateL.egsSelectedGoalie
goalie <- nth gid $ s^.database.dbGoalies
Just $ "Goalie: " ++ goalieDetails goalie ++ "\n\n"
in str $ hdr ++ "Are you sure you want to delete this goalie? (Y/N)"
, handleController = \e -> case ynHandler e of
Just True -> do
use (progMode.editGoalieStateL.egsSelectedGoalie) >>= mapM_
(\gid -> database.dbGoalies %= dropNth gid)
modify edit
Just False -> progMode.editGoalieStateL.egsMode .= EGMenu
Nothing -> return ()
}
ytdGoalsC :: Controller
ytdGoalsC = promptController editGoalieYtdGoalsPrompt
ytdGamesC :: Bool -> Action () -> Controller
ytdGamesC = curry $ promptController .
uncurry editGoalieYtdGamesPrompt
ytdWinsC :: Controller
ytdWinsC = promptController editGoalieYtdWinsPrompt
ytdMinsC :: Bool -> Action () -> Controller
ytdMinsC = curry $ promptController .
uncurry editGoalieYtdMinsPrompt
ytdLossesC :: Controller
ytdLossesC = promptController editGoalieYtdLossesPrompt
ytdGoalsC :: Bool -> Action () -> Controller
ytdGoalsC = curry $ promptController .
uncurry editGoalieYtdGoalsPrompt
ytdTiesC :: Controller
ytdTiesC = promptController editGoalieYtdTiesPrompt
ytdShutoutsC :: Bool -> Action () -> Controller
ytdShutoutsC = curry $ promptController .
uncurry editGoalieYtdShutoutsPrompt
ltGamesC :: Controller
ltGamesC = promptController editGoalieLtGamesPrompt
ytdWinsC :: Bool -> Action () -> Controller
ytdWinsC = curry $ promptController .
uncurry editGoalieYtdWinsPrompt
ltMinsC :: Controller
ltMinsC = promptController editGoalieLtMinsPrompt
ytdLossesC :: Bool -> Action () -> Controller
ytdLossesC = curry $ promptController .
uncurry editGoalieYtdLossesPrompt
ltGoalsC :: Controller
ltGoalsC = promptController editGoalieLtGoalsPrompt
ytdTiesC :: Action () -> Controller
ytdTiesC = promptController . editGoalieYtdTiesPrompt
ltWinsC :: Controller
ltWinsC = promptController editGoalieLtWinsPrompt
ltGamesC :: Bool -> Action () -> Controller
ltGamesC = curry $ promptController .
uncurry editGoalieLtGamesPrompt
ltLossesC :: Controller
ltLossesC = promptController editGoalieLtLossesPrompt
ltMinsC :: Bool -> Action () -> Controller
ltMinsC = curry $ promptController .
uncurry editGoalieLtMinsPrompt
ltTiesC :: Controller
ltTiesC = promptController editGoalieLtTiesPrompt
ltGoalsC :: Bool -> Action() -> Controller
ltGoalsC = curry $ promptController .
uncurry editGoalieLtGoalsPrompt
header :: ProgState -> C.Update ()
header s = C.drawString $ fromMaybe "" $ do
gid <- s^.progMode.editGoalieStateL.egsSelectedGoalie
g <- nth gid $ s^.database.dbGoalies
Just $ goalieDetails g ++ "\n"
ltShutoutsC :: Bool -> Action () -> Controller
ltShutoutsC = curry $ promptController .
uncurry editGoalieLtShutoutsPrompt
ltWinsC :: Bool -> Action () -> Controller
ltWinsC = curry $ promptController .
uncurry editGoalieLtWinsPrompt
ltLossesC :: Bool -> Action () -> Controller
ltLossesC = curry $ promptController .
uncurry editGoalieLtLossesPrompt
ltTiesC :: Action () -> Controller
ltTiesC = promptController . editGoalieLtTiesPrompt
header :: ProgState -> Widget () -> Widget ()
header s w = vBox
[ str $ fromMaybe "" $ do
gid <- s^.progMode.editGoalieStateL.egsSelectedGoalie
g <- nth gid $ s^.database.dbGoalies
Just $ goalieDetails g
, w
]

View File

@@ -1,7 +1,7 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -21,10 +21,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
module Mtlstats.Control.EditPlayer (editPlayerC) where
import Brick.Types (Widget)
import Brick.Widgets.Core (emptyWidget, str, vBox)
import Control.Monad.State.Class (modify)
import Data.Maybe (fromMaybe)
import Lens.Micro ((^.))
import qualified UI.NCurses as C
import Lens.Micro.Mtl ((.=), (%=), use)
import Mtlstats.Actions
import Mtlstats.Handlers
import Mtlstats.Helpers.Player
import Mtlstats.Menu
import Mtlstats.Menu.EditPlayer
@@ -37,61 +42,89 @@ import Mtlstats.Util
editPlayerC :: EditPlayerState -> Controller
editPlayerC eps
| null $ eps^.epsSelectedPlayer = selectPlayerC
| otherwise = case eps^.epsMode of
EPMenu -> menuC
EPNumber -> numberC
EPName -> nameC
EPPosition -> positionC
EPYtd -> ytdC
EPLifetime -> lifetimeC
EPYtdGoals -> ytdGoalsC
EPYtdAssists -> ytdAssistsC
EPYtdPMin -> ytdPMinC
EPLtGoals -> ltGoalsC
EPLtAssists -> ltAssistsC
EPLtPMin -> ltPMinC
| otherwise =
( case eps^.epsMode of
EPMenu -> menuC
EPNumber -> numberC
EPName -> nameC
EPPosition -> positionC
EPYtd -> ytdC
EPLifetime -> lifetimeC
EPDelete -> deleteC
EPYtdGoals b -> ytdGoalsC b
EPYtdAssists b -> ytdAssistsC b
EPYtdPMin -> ytdPMinC
EPLtGoals b -> ltGoalsC b
EPLtAssists b -> ltAssistsC b
EPLtPMin -> ltPMinC
) $ eps^.epsCallback
selectPlayerC :: Controller
selectPlayerC = promptController playerToEditPrompt
menuC :: Controller
menuC = menuControllerWith header editPlayerMenu
menuC :: Action () -> Controller
menuC _ = menuControllerWith header editPlayerMenu
numberC :: Controller
numberC = promptController editPlayerNumPrompt
numberC :: Action () -> Controller
numberC = promptController . editPlayerNumPrompt
nameC :: Controller
nameC = promptController editPlayerNamePrompt
nameC :: Action () -> Controller
nameC = promptController . editPlayerNamePrompt
positionC :: Controller
positionC = promptController editPlayerPosPrompt
positionC :: Action () -> Controller
positionC = promptController . editPlayerPosPrompt
ytdC :: Controller
ytdC = menuControllerWith header editPlayerYtdMenu
ytdC :: Action () -> Controller
ytdC _ = menuControllerWith header editPlayerYtdMenu
lifetimeC :: Controller
lifetimeC = menuControllerWith header editPlayerLtMenu
lifetimeC :: Action () -> Controller
lifetimeC _ = menuControllerWith header editPlayerLtMenu
ytdGoalsC :: Controller
ytdGoalsC = promptController editPlayerYtdGoalsPrompt
deleteC :: Action () -> Controller
deleteC _ = Controller
ytdAssistsC :: Controller
ytdAssistsC = promptController editPlayerYtdAssistsPrompt
{ drawController = \s -> let
hdr = fromMaybe [] $ do
pid <- s^.progMode.editPlayerStateL.epsSelectedPlayer
player <- nth pid $ s^.database.dbPlayers
Just $ "Player: " ++ playerDetails player ++ "\n"
in str $ hdr ++ "Are you sure you want to delete this player? (Y/N)"
, handleController = \e -> case ynHandler e of
Just True -> do
use (progMode.editPlayerStateL.epsSelectedPlayer) >>= mapM_
(\pid -> database.dbPlayers %= dropNth pid)
modify edit
Just False -> progMode.editPlayerStateL.epsMode .= EPMenu
Nothing -> return ()
}
ytdPMinC :: Controller
ytdPMinC = promptController editPlayerYtdPMinPrompt
ytdGoalsC :: Bool -> Action () -> Controller
ytdGoalsC batchMode callback = promptController $
editPlayerYtdGoalsPrompt batchMode callback
ltGoalsC :: Controller
ltGoalsC = promptController editPlayerLtGoalsPrompt
ytdAssistsC :: Bool -> Action () -> Controller
ytdAssistsC batchMode callback = promptController $
editPlayerYtdAssistsPrompt batchMode callback
ltAssistsC :: Controller
ltAssistsC = promptController editPlayerLtAssistsPrompt
ytdPMinC :: Action () -> Controller
ytdPMinC = promptController . editPlayerYtdPMinPrompt
ltPMinC :: Controller
ltPMinC = promptController editPlayerLtPMinPrompt
ltGoalsC :: Bool -> Action () -> Controller
ltGoalsC batchMode callback = promptController $
editPlayerLtGoalsPrompt batchMode callback
header :: ProgState -> C.Update ()
header s = C.drawString $ fromMaybe "" $ do
pid <- s^.progMode.editPlayerStateL.epsSelectedPlayer
player <- nth pid $ s^.database.dbPlayers
Just $ playerDetails player ++ "\n"
ltAssistsC :: Bool -> Action () -> Controller
ltAssistsC batchMode callback = promptController $
editPlayerLtAssistsPrompt batchMode callback
ltPMinC :: Action () -> Controller
ltPMinC = promptController . editPlayerLtPMinPrompt
header :: ProgState -> Widget () -> Widget ()
header s w = vBox
[ fromMaybe emptyWidget $ do
pid <- s^.progMode.editPlayerStateL.epsSelectedPlayer
player <- nth pid $ s^.database.dbPlayers
Just $ str $ playerDetails player
, w
]

View File

@@ -0,0 +1,91 @@
{- |
mtlstats
Copyright (C) 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/>.
-}
{-# LANGUAGE LambdaCase #-}
module Mtlstats.Control.EditStandings (editStandingsC) where
import Brick.Types (Widget)
import Brick.Widgets.Core (vBox)
import Lens.Micro ((^.))
import Mtlstats.Format
import Mtlstats.Menu
import Mtlstats.Menu.EditStandings
import Mtlstats.Prompt
import Mtlstats.Prompt.EditStandings
import Mtlstats.Types
import Mtlstats.Types.Menu
import Mtlstats.Util
-- | Controller for the edit standings menu
editStandingsC :: EditStandingsMode -> Controller
editStandingsC = \case
ESMMenu -> menuControllerWith header editStandingsMenu
ESMHome m -> editHomeStandingsC m
ESMAway m -> editAwayStandingsC m
editHomeStandingsC :: ESMSubMode -> Controller
editHomeStandingsC = \case
ESMSubMenu -> menuC editHomeStandingsMenu
ESMEditWins -> promptC editHomeWinsPrompt
ESMEditLosses -> promptC editHomeLossesPrompt
ESMEditOvertime -> promptC editHomeOvertimePrompt
ESMEditGoalsFor -> promptC editHomeGoalsForPrompt
ESMEditGoalsAgainst -> promptC editHomeGoalsAgainstPrompt
editAwayStandingsC :: ESMSubMode -> Controller
editAwayStandingsC = \case
ESMSubMenu -> menuC editAwayStandingsMenu
ESMEditWins -> promptC editAwayWinsPrompt
ESMEditLosses -> promptC editAwayLossesPrompt
ESMEditOvertime -> promptC editAwayOvertimePrompt
ESMEditGoalsFor -> promptC editAwayGoalsForPrompt
ESMEditGoalsAgainst -> promptC editAwayGoalsAgainstPrompt
menuC :: Menu () -> Controller
menuC = menuControllerWith header
promptC :: Prompt -> Controller
promptC = promptControllerWith header
header :: ProgState -> Widget () -> Widget ()
header s w = let
db = s^.database
home = db^.dbHomeGameStats
away = db^.dbAwayGameStats
table = numTable [" W", " L", " OT", " GF", " GA"]
[ ( "HOME", valsFor home )
, ( "ROAD", valsFor away )
]
in vBox
[ linesToWidget $ table ++ [""]
, w
]
valsFor :: GameStats -> [Int]
valsFor gs =
[ gs^.gmsWins
, gs^.gmsLosses
, gs^.gmsOvertime
, gs^.gmsGoalsFor
, gs^.gmsGoalsAgainst
]

View File

@@ -1,7 +1,7 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -21,13 +21,27 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
module Mtlstats.Control.NewGame (newGameC) where
import Control.Monad.Trans.State (gets, modify)
import Brick.Main (vScrollBy, vScrollToBeginning)
import Brick.Types
( BrickEvent (VtyEvent)
, ViewportType (Vertical)
, Widget
)
import Brick.Widgets.Center (hCenter)
import Brick.Widgets.Core (str, vBox, viewport)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.State.Class (get, gets, modify)
import Data.Maybe (fromJust, fromMaybe, isJust)
import Graphics.Vty.Input.Events
( Event (EvKey)
, Key (KDown, KHome, KEnter, KUp)
)
import Lens.Micro ((^.), (.~))
import qualified UI.NCurses as C
import Lens.Micro.Mtl ((.=), use)
import Mtlstats.Actions
import Mtlstats.Actions.NewGame
import Mtlstats.Config
import Mtlstats.Control.NewGame.GoalieInput
import Mtlstats.Format
import Mtlstats.Handlers
@@ -60,7 +74,7 @@ gameYearC :: Controller
gameYearC = promptControllerWith header gameYearPrompt
gameMonthC :: Controller
gameMonthC = menuControllerWith header gameMonthMenu
gameMonthC = promptControllerWith monthHeader gameMonthPrompt
gameDayC :: Controller
gameDayC = promptControllerWith header gameDayPrompt
@@ -79,32 +93,30 @@ awayScoreC = promptControllerWith header awayScorePrompt
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
modify $ progMode.gameStateL.overtimeFlag .~ ynHandler e
return True
{ drawController = \s -> header s $
str "Did the game go into overtime? (Y/N)"
, handleController = \e ->
progMode.gameStateL.overtimeFlag .= ynHandler e
}
verifyDataC :: Controller
verifyDataC = Controller
{ drawController = \s -> do
let gs = s^.progMode.gameStateL
header s
C.drawString "\n"
C.drawString $ unlines $ labelTable
{ drawController = \s -> let
gs = s^.progMode.gameStateL
in header s $ linesToWidget $
[""] ++
labelTable
[ ( "Date", gameDate gs )
, ( "Game type", show $ fromJust $ gs^.gameType )
, ( "Other team", gs^.otherTeam )
, ( "Home score", show $ fromJust $ gs^.homeScore )
, ( "Away score", show $ fromJust $ gs^.awayScore )
, ( "Overtime", show $ fromJust $ gs^.overtimeFlag )
]
C.drawString "\nIs the above information correct? (Y/N)"
return C.CursorInvisible
, handleController = \e -> do
] ++
[ ""
, "Is the above information correct? (Y/N)"
]
, handleController = \e ->
case ynHandler e of
Just True -> modify
$ (progMode.gameStateL.dataVerified .~ True)
@@ -112,7 +124,6 @@ verifyDataC = Controller
. awardShutouts
Just False -> modify $ progMode.gameStateL .~ newGameState
Nothing -> return ()
return True
}
goalInput :: GameState -> Controller
@@ -129,7 +140,6 @@ recordGoalC = Controller
, handleController = \e -> do
(game, goal) <- gets gameGoal
promptHandler (recordGoalPrompt game goal) e
return True
}
recordAssistC :: Controller
@@ -140,86 +150,100 @@ recordAssistC = Controller
, 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
{ drawController = \s -> let
(game, goal) = gameGoal s
gs = s^.progMode.gameStateL
players = s^.database.dbPlayers
msg =
[ " 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)"
]
in linesToWidget msg
, 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
{ drawController = \s -> header s $
drawPrompt pMinPlayerPrompt s
, handleController = \e -> do
promptHandler pMinPlayerPrompt e
return True
, handleController = promptHandler pMinPlayerPrompt
}
getPMinsC :: Controller
getPMinsC = Controller
{ drawController = \s -> do
header s
C.drawString $ fromMaybe "" $ do
{ drawController = \s -> header s $ vBox
[ str $ fromMaybe "" $ do
pid <- s^.progMode.gameStateL.gameSelectedPlayer
player <- nth pid $ s^.database.dbPlayers
Just $ playerSummary player ++ "\n"
drawPrompt assignPMinsPrompt s
, handleController = \e -> do
promptHandler assignPMinsPrompt e
return True
Just $ playerSummary player
, drawPrompt assignPMinsPrompt s
]
, handleController = promptHandler assignPMinsPrompt
}
reportC :: Controller
reportC = Controller
{ drawController = \s -> do
(rows, cols) <- C.windowSize
C.drawString $ unlines $ slice
(s^.scrollOffset)
(fromInteger $ pred rows)
(report (fromInteger $ pred cols) s)
return C.CursorInvisible
{ drawController = viewport () Vertical . hCenter . linesToWidget .
displayReport reportCols
, handleController = \e -> do
scr <- use scroller
case e of
C.EventSpecialKey C.KeyUpArrow -> modify scrollUp
C.EventSpecialKey C.KeyDownArrow -> modify scrollDown
C.EventSpecialKey C.KeyHome -> modify $ scrollOffset .~ 0
C.EventSpecialKey _ -> modify backHome
C.EventCharacter _ -> modify backHome
_ -> return ()
return True
VtyEvent (EvKey k []) -> case k of
KUp -> vScrollBy scr (-1)
KDown -> vScrollBy scr 1
KHome -> vScrollToBeginning scr
KEnter -> do
get >>= liftIO . writeFile reportFilename . exportReport reportCols
modify backHome
_ -> return ()
_ -> return ()
}
header :: ProgState -> C.Update ()
header s = C.drawString $
"*** GAME " ++ padNum 2 (s^.database.dbGames) ++ " ***\n"
header :: ProgState -> Widget () -> Widget ()
header s w = vBox
[ str $ "*** GAME " ++ padNum 2 (s^.database.dbGames) ++ " ***\n"
, w
]
monthHeader :: ProgState -> Widget () -> Widget ()
monthHeader s w = let
table = labelTable $ zip (map show ([1..] :: [Int]))
[ "JANUARY"
, "FEBRUARY"
, "MARCH"
, "APRIL"
, "MAY"
, "JUNE"
, "JULY"
, "AUGUST"
, "SEPTEMBER"
, "OCTOBER"
, "NOVEMBER"
, "DECEMBER"
]
in header s $ vBox
[ linesToWidgetC $
["MONTH:", ""] ++ table ++ [""]
, w
]
gameGoal :: ProgState -> (Int, Int)
gameGoal s =

View File

@@ -1,7 +1,7 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -21,9 +21,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
module Mtlstats.Control.NewGame.GoalieInput (goalieInputC) where
import Brick.Types (Widget)
import Brick.Widgets.Core (str, vBox)
import Data.Maybe (fromMaybe)
import Lens.Micro ((^.))
import qualified UI.NCurses as C
import Mtlstats.Format
import Mtlstats.Menu
@@ -52,11 +53,11 @@ goalsAllowedC = promptControllerWith header goalsAllowedPrompt
selectGameGoalieC :: Controller
selectGameGoalieC = menuStateController gameGoalieMenu
header :: ProgState -> C.Update ()
header s = C.drawString $ unlines
header :: ProgState -> Widget () -> Widget ()
header s w = vBox $ map str
[ "*** GAME " ++ padNum 2 (s^.database.dbGames) ++ " ***"
, fromMaybe "" $ do
n <- s^.progMode.gameStateL.gameSelectedGoalie
g <- nth n $ s^.database.dbGoalies
Just $ goalieSummary g
]
] ++ [w]

View File

@@ -0,0 +1,139 @@
{- |
mtlstats
Copyright (C) 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/>.
-}
{-# LANGUAGE LambdaCase #-}
module Mtlstats.Control.TitleScreen (titleScreenC) where
import Brick.Types (BrickEvent (VtyEvent))
import Control.Monad.State.Class (modify)
import Data.Char (chr)
import Graphics.Vty.Input.Events (Event (EvKey))
import Mtlstats.Actions
import Mtlstats.Types
import Mtlstats.Util
titleScreenC :: Controller
titleScreenC = Controller
{ drawController = const $ linesToWidgetC
$ [ ""
, "MONTREAL CANADIENS STATISTICS"
]
++ titleText
++ [ ""
, "Copyright (C) 1984, 1985, 2019-2021, 2023 Rhéal Lamothe"
, "<rheal.lamothe@gmail.com>"
, ""
, "Press any key to continue..."
]
, handleController = \case
VtyEvent (EvKey _ _) -> modify backHome
_ -> return ()
}
titleText :: [String]
titleText = box $ map (map blockify) $ foldl joinBlocks (repeat "")
[chM, chT, chL, chS, chT, chA, chT, chS]
box :: [String] -> [String]
box strs
= [[tl] ++ replicate width horiz ++ [tr]]
++ map (\s -> [vert] ++ s ++ [vert]) strs
++ [[bl] ++ replicate width horiz ++ [br]]
where
width = length $ head strs
tl = chr 0x2554
tr = chr 0x2557
bl = chr 0x255a
br = chr 0x255d
horiz = chr 0x2550
vert = chr 0x2551
blockify :: Char -> Char
blockify = \case
'#' -> chr 0x2588
'>' -> chr 0x2590
'<' -> chr 0x258c
ch -> ch
joinBlocks :: [String] -> [String] -> [String]
joinBlocks = zipWith (++)
chM :: [String]
chM =
[ "##< >##"
, ">## ##<"
, ">##< >##<"
, ">### ###<"
, ">#######<"
, ">#<###>#<"
, ">#<>#<>#<"
, "##< >##"
]
chT :: [String]
chT =
[ ">########<"
, ">## ## ##<"
, ">#< ## >#<"
, " ## "
, " ## "
, " ## "
, " ## "
, " >##< "
]
chL :: [String]
chL =
[ "### "
, ">#< "
, ">#< "
, ">#< "
, ">#< "
, ">#< ##"
, ">#< >##"
, "#######"
]
chS :: [String]
chS =
[ " #####< "
, ">#< ## "
, "## "
, " #####< "
, " >#<"
, " ##"
, ">#< >#<"
, " ###### "
]
chA :: [String]
chA =
[ " >##< "
, " ## "
, " >##< "
, " #### "
, " >#<>#< "
, " ###### "
, ">#< >#<"
, "### ###"
]

View File

@@ -1,7 +1,7 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -129,9 +129,14 @@ month _ = ""
labelTable :: [(String, String)] -> [String]
labelTable xs = let
labelWidth = maximum $ map (length . fst) xs
valWidth = maximum $ map (length . snd) xs
in map
(\(label, val) -> right labelWidth label ++ ": " ++ val)
xs
( \(label, val)
-> right labelWidth label
++ ": "
++ left valWidth val
) xs
-- | Creates a variable column table of numbers with two axes
numTable

View File

@@ -1,7 +1,7 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -21,12 +21,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
module Mtlstats.Handlers (ynHandler) where
import Brick.Types (BrickEvent (VtyEvent))
import Data.Char (toUpper)
import qualified UI.NCurses as C
import Graphics.Vty.Input.Events (Event (EvKey), Key (KChar))
-- | Handler for a yes/no prompt
ynHandler :: C.Event -> Maybe Bool
ynHandler (C.EventCharacter c) = case toUpper c of
ynHandler :: BrickEvent () () -> Maybe Bool
ynHandler (VtyEvent (EvKey (KChar c) [])) = case toUpper c of
'Y' -> Just True
'N' -> Just False
_ -> Nothing

View File

@@ -1,7 +1,7 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -19,7 +19,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
-}
module Mtlstats.Helpers.Goalie (goalieDetails) where
module Mtlstats.Helpers.Goalie (goalieDetails, goalieName) where
import Lens.Micro ((^.))
@@ -31,7 +31,7 @@ goalieDetails :: Goalie -> String
goalieDetails g = let
header = unlines $ labelTable
[ ( "Number", show $ g^.gNumber )
, ( "Name", g^.gName )
, ( "Name", goalieName g )
]
body = unlines $ numTable ["YTD", "Lifetime"] $ map
@@ -39,9 +39,24 @@ goalieDetails g = let
[ ( "Games played", gsGames )
, ( "Mins played", gsMinsPlayed )
, ( "Goals allowed", gsGoalsAllowed )
, ( "Shutouts", gsShutouts )
, ( "Wins", gsWins )
, ( "Losses", gsLosses )
, ( "Ties", gsTies )
]
in header ++ "\n" ++ body
-- | Returns the goalie name, modified if they are a rookie
goalieName :: Goalie -> String
goalieName g = let
prefix = if g^.gActive
then ""
else "*"
suffix = if g^.gRookie
then "*"
else ""
in prefix ++ g^.gName ++ suffix

View File

@@ -1,7 +1,7 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -19,7 +19,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
-}
module Mtlstats.Helpers.Player (playerDetails) where
module Mtlstats.Helpers.Player (playerDetails, playerName) where
import Lens.Micro ((^.))
@@ -32,7 +32,7 @@ playerDetails p = unlines $ top ++ [""] ++ table
where
top = labelTable
[ ( "Number", show $ p^.pNumber )
, ( "Name", p^.pName )
, ( "Name", playerName p )
, ( "Position", p^.pPosition )
]
@@ -43,3 +43,18 @@ playerDetails p = unlines $ top ++ [""] ++ table
, ( "Assists", psAssists )
, ( "Penalty mins", psPMin )
]
-- | Presents a modified version of the player's name indicating
-- whether or not they're a rookie
playerName :: Player -> String
playerName p = let
prefix = if p^.pActive
then ""
else "*"
suffix = if p^.pRookie
then "*"
else ""
in prefix ++ p^.pName ++ suffix

View File

@@ -0,0 +1,89 @@
{- |
mtlstats
Copyright (C) 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/>.
-}
{-# LANGUAGE LambdaCase #-}
module Mtlstats.Helpers.Position
( posSearch
, posSearchExact
, posCallback
, getPositions
) where
import Data.Char (toUpper)
import Data.List (isInfixOf)
import Data.Maybe (fromMaybe)
import qualified Data.Set as S
import Lens.Micro ((^.), to)
import Lens.Micro.Mtl (use)
import Mtlstats.Types
import Mtlstats.Util
-- | Searches the 'Database' for all the positions used
posSearch
:: String
-- ^ The search string
-> Database
-- ^ The database
-> [(Int, String)]
-- ^ A list of result indices and their values
posSearch sStr db = filter sFunc $ zip [0..] ps
where
sFunc (_, pos) = map toUpper sStr `isInfixOf` map toUpper pos
ps = getPositions db
-- | Searches the 'Database' for an exact position
posSearchExact
:: String
-- ^ The search string
-> Database
-- ^ The database
-> Maybe Int
-- ^ The index of the result (or 'Nothing' if not found)
posSearchExact sStr db = case filter sFunc $ zip [0..] ps of
[] -> Nothing
(n,_):_ -> Just n
where
sFunc (_, pos) = sStr == pos
ps = getPositions db
-- | Builds a callback function for when a 'Player' position is
-- selected
posCallback
:: (String -> Action ())
-- ^ The raw callback function
-> Maybe Int
-- ^ The index number of the position selected or 'Nothing' if blank
-> Action ()
-- ^ The action to perform
posCallback callback = \case
Nothing -> callback ""
Just n -> do
ps <- use (database.to getPositions)
let pos = fromMaybe "" $ nth n ps
callback pos
-- | Extracts a list of positions from a 'Database'
getPositions :: Database -> [String]
getPositions = do
raw <- map (^.pPosition) . (^.dbPlayers)
return $ S.toList $ S.fromList raw

View File

@@ -1,7 +1,7 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -29,53 +29,42 @@ module Mtlstats.Menu (
-- * Menus
mainMenu,
newSeasonMenu,
gameMonthMenu,
gameTypeMenu,
gameGoalieMenu,
editMenu
) where
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.State (gets, modify)
import Data.Aeson (encodeFile)
import Brick.Main (halt)
import Brick.Types (BrickEvent (VtyEvent), Widget)
import Control.Monad.State.Class (gets, modify)
import Data.Char (toUpper)
import qualified Data.Map as M
import Data.Maybe (mapMaybe)
import Graphics.Vty.Input.Events (Event (EvKey), Key (KChar))
import Lens.Micro ((^.), (?~))
import Lens.Micro.Extras (view)
import System.EasyFile
( createDirectoryIfMissing
, getAppUserDataDirectory
, (</>)
)
import qualified UI.NCurses as C
import Mtlstats.Actions
import qualified Mtlstats.Actions.NewGame.GoalieInput as GI
import Mtlstats.Config
import Mtlstats.Actions.EditStandings
import Mtlstats.Types
import Mtlstats.Types.Menu
import Mtlstats.Util
-- | Generates a simple 'Controller' for a Menu
menuController :: Menu () -> Controller
menuController = menuControllerWith $ const $ return ()
menuController = menuControllerWith $ const id
-- | Generate a simple 'Controller' for a 'Menu' with a header
menuControllerWith
:: (ProgState -> C.Update ())
-- ^ Generates the header
:: (ProgState -> Widget () -> Widget())
-- ^ Function to attach the header
-> Menu ()
-- ^ The menu
-> Controller
-- ^ The resulting controller
menuControllerWith header menu = Controller
{ drawController = \s -> do
header s
drawMenu menu
, handleController = \e -> do
menuHandler menu e
return True
{ drawController = \s -> header s $ drawMenu menu
, handleController = menuHandler menu
}
-- | Generate and create a controller for a menu based on the current
@@ -90,80 +79,54 @@ menuStateController menuFunc = Controller
, handleController = \e -> do
menu <- gets menuFunc
menuHandler menu e
return True
}
-- | The draw function for a 'Menu'
drawMenu :: Menu a -> C.Update C.CursorMode
drawMenu m = do
C.drawString $ show m
return C.CursorInvisible
drawMenu :: Menu a -> Widget ()
drawMenu m = let
menuLines = lines $ show m
in linesToWidgetC menuLines
-- | The event handler for a 'Menu'
menuHandler :: Menu a -> C.Event -> Action a
menuHandler m (C.EventCharacter c) =
menuHandler :: Menu a -> Handler a
menuHandler m (VtyEvent (EvKey (KChar c) [])) =
case filter (\i -> i^.miKey == toUpper c) $ m^.menuItems of
i:_ -> i^.miAction
[] -> return $ m^.menuDefault
menuHandler m _ = return $ m^.menuDefault
-- | The main menu
mainMenu :: Menu Bool
mainMenu = Menu "*** MAIN MENU ***" True
[ MenuItem '1' "New Season" $
modify startNewSeason >> return True
, MenuItem '2' "New Game" $
modify startNewGame >> return True
, MenuItem '3' "Edit" $
modify edit >> return True
, MenuItem 'X' "Exit" $ do
db <- gets $ view database
liftIO $ do
dir <- getAppUserDataDirectory appName
let dbFile = dir </> dbFname
createDirectoryIfMissing True dir
encodeFile dbFile db
return False
mainMenu :: Menu ()
mainMenu = Menu "MASTER MENU" ()
[ MenuItem 'A' "NEW SEASON" $
modify startNewSeason
, MenuItem 'B' "NEW GAME" $
modify startNewGame
, MenuItem 'C' "EDIT MENU" $
modify edit
, MenuItem 'E' "EXIT" $
saveDatabase >> halt
]
-- | The new season menu
newSeasonMenu :: Menu ()
newSeasonMenu = Menu "*** SEASON TYPE ***" ()
[ MenuItem 'R' "Regular Season" $ modify
newSeasonMenu = Menu "SEASON TYPE" ()
[ MenuItem 'R' "REGULAR SEASON" $ modify
$ resetYtd
. clearRookies
. resetStandings
. startNewGame
, MenuItem 'P' "Playoffs" $ modify
. backHome
, MenuItem 'P' "PLAYOFFS" $ modify
$ resetStandings
. startNewGame
]
-- | Requests the month in which the game took place
gameMonthMenu :: Menu ()
gameMonthMenu = Menu "Month:" () $ map
(\(ch, name, val) ->
MenuItem ch name $
modify $ progMode.gameStateL.gameMonth ?~ val)
[ ( 'A', "January", 1 )
, ( 'B', "February", 2 )
, ( 'C', "March", 3 )
, ( 'D', "April", 4 )
, ( 'E', "May", 5 )
, ( 'F', "June", 6 )
, ( 'G', "July", 7 )
, ( 'H', "August", 8 )
, ( 'I', "September", 9 )
, ( 'J', "October", 10 )
, ( 'K', "November", 11 )
, ( 'L', "December", 12 )
. backHome
]
-- | The game type menu (home/away)
gameTypeMenu :: Menu ()
gameTypeMenu = Menu "Game type:" ()
[ MenuItem '1' "Home Game" $
gameTypeMenu = Menu "GAME TYPE:" ()
[ MenuItem 'H' "HOME GAME" $
modify $ progMode.gameStateL.gameType ?~ HomeGame
, MenuItem '2' "Away Game" $
, MenuItem 'A' "AWAY GAME" $
modify $ progMode.gameStateL.gameType ?~ AwayGame
]
@@ -177,22 +140,25 @@ gameGoalieMenu s = let
goalie <- nth n $ s^.database.dbGoalies
Just (n, goalie))
gids
in Menu title () $ map
(\(ch, (gid, goalie)) -> MenuItem ch (goalieSummary goalie) $
modify $ GI.setGameGoalie gid) $
zip ['1'..] goalies
in Menu title () $ zipWith
(\ch (gid, goalie) -> MenuItem ch (goalieSummary goalie) $
modify $ GI.setGameGoalie gid)
['1'..]
goalies
-- | The edit menu
editMenu :: Menu ()
editMenu = Menu "*** EDIT ***" ()
[ MenuItem '1' "Create Player" $
editMenu = Menu "EDIT MENU" ()
[ MenuItem 'A' "CREATE PLAYER" $
modify createPlayer
, MenuItem '2' "Create Goalie" $
, MenuItem 'B' "CREATE GOALIE" $
modify createGoalie
, MenuItem '3' "Edit Player" $
, MenuItem 'C' "EDIT PLAYER" $
modify editPlayer
, MenuItem '4' "Edit Goalie" $
, MenuItem 'D' "EDIT GOALIE" $
modify editGoalie
, MenuItem 'R' "Return to Main Menu" $
, MenuItem 'E' "EDIT STANDINGS" $
modify editStandings
, MenuItem 'R' "RETURN TO MAIN MENU" $
modify backHome
]

View File

@@ -1,7 +1,7 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -25,8 +25,8 @@ module Mtlstats.Menu.EditGoalie
, editGoalieLtMenu
) where
import Control.Monad.Trans.State (modify)
import Lens.Micro ((.~))
import Control.Monad.State.Class (modify)
import Lens.Micro ((.~), (%~))
import Mtlstats.Actions
import Mtlstats.Types
@@ -34,44 +34,54 @@ import Mtlstats.Types.Menu
-- | The 'Goalie' edit menu
editGoalieMenu :: Menu ()
editGoalieMenu = Menu "*** EDIT GOALTENDER ***" () $ map
(\(ch, label, mode) -> MenuItem ch label $
modify $ case mode of
Nothing -> edit
Just m -> progMode.editGoalieStateL.egsMode .~ m)
editGoalieMenu = Menu "EDIT GOALTENDER" () $ map
(\(ch, label, action) -> MenuItem ch label $ modify action)
-- key, label, value
[ ( '1', "Edit number", Just EGNumber )
, ( '2', "Edit name", Just EGName )
, ( '3', "Edit YTD stats", Just EGYtd )
, ( '4', "Edit Lifetime stats", Just EGLifetime )
, ( 'R', "Return to Edit Menu", Nothing )
[ ( 'A', "NUMBER", set EGNumber )
, ( 'B', "NAME", set EGName )
, ( 'C', "ROOKIE FLAG", toggleRookie )
, ( 'D', "ACTIVE FLAG", toggleActive )
, ( 'E', "YTD STATS", set EGYtd )
, ( 'F', "LIFETIME STATS", set EGLifetime )
, ( 'G', "DELETE RECORD", set EGDelete )
, ( 'R', "RETURN TO EDIT MENU", edit )
]
where
set mode = progMode.editGoalieStateL.egsMode .~ mode
toggleRookie = editSelectedGoalie (gRookie %~ not)
toggleActive = editSelectedGoalie (gActive %~ not)
-- | The 'Goalie' YTD edit menu
editGoalieYtdMenu :: Menu ()
editGoalieYtdMenu = editMenu "*** EDIT GOALTENDER YEAR-TO-DATE ***"
editGoalieYtdMenu = editMenu "EDIT GOALTENDER YEAR-TO-DATE"
-- key, label, value
[ ( '1', "Edit YTD games", EGYtdGames )
, ( '2', "Edit YTD minutes", EGYtdMins )
, ( '3', "Edit YTD goals", EGYtdGoals )
, ( '4', "Edit YTD wins", EGYtdWins )
, ( '5', "Edit YTD losses", EGYtdLosses )
, ( '6', "Edit YTD ties", EGYtdTies )
, ( 'R', "Return to edit menu", EGMenu )
[ ( 'A', "ALL YTD STATS", EGYtdGames True )
, ( 'B', "YTD GAMES", EGYtdGames False )
, ( 'C', "YTD MINUTES", EGYtdMins False )
, ( 'D', "YTD GOALS", EGYtdGoals False )
, ( 'E', "YTD SHUTOUTS", EGYtdShutouts False )
, ( 'F', "YTD WINS", EGYtdWins False )
, ( 'G', "YTD LOSSES", EGYtdLosses False )
, ( 'H', "YTD TIES", EGYtdTies )
, ( 'R', "RETURN TO EDIT MENU", EGMenu )
]
-- | The 'Goalie' lifetime edit menu
editGoalieLtMenu :: Menu ()
editGoalieLtMenu = editMenu
"*** EDIT GOALTENDER LIFETIME ***"
-- key, label, value
[ ( '1', "Edit lifetime games", EGLtGames )
, ( '2', "Edit lifetime minutes", EGLtMins )
, ( '3', "Edit lifetime goals", EGLtGoals )
, ( '4', "Edit lifetime wins", EGLtWins )
, ( '5', "Edit lifetime losses", EGLtLosses )
, ( '6', "Edit lifetime ties", EGLtTies )
, ( 'R', "Return to edit menu", EGMenu )
"EDIT GOALTENDER LIFETIME"
-- key, label, value
[ ( 'A', "ALL LIFETIME STATS", EGLtGames True )
, ( 'B', "LIFETIME GAMES", EGLtGames False )
, ( 'C', "LIFETIME MINUTES", EGLtMins False )
, ( 'D', "LIFETIME GOALS", EGLtGoals False )
, ( 'E', "LIFETIME SHUTOUTS", EGLtShutouts False )
, ( 'F', "LIFETIME WINS", EGLtWins False )
, ( 'G', "LIFETIME LOSSES", EGLtLosses False )
, ( 'H', "LIFETIME TIES", EGLtTies )
, ( 'R', "RETURN TO EDIT MENU", EGMenu )
]
editMenu :: String -> [(Char, String, EditGoalieMode)] -> Menu ()

View File

@@ -1,7 +1,7 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -25,8 +25,8 @@ module Mtlstats.Menu.EditPlayer
, editPlayerLtMenu
) where
import Control.Monad.Trans.State (modify)
import Lens.Micro ((.~))
import Control.Monad.State.Class (modify)
import Lens.Micro ((.~), (%~))
import Mtlstats.Actions
import Mtlstats.Types
@@ -34,40 +34,48 @@ import Mtlstats.Types.Menu
-- | The 'Player' edit menu
editPlayerMenu :: Menu ()
editPlayerMenu = Menu "*** EDIT PLAYER ***" () $ map
(\(ch, label, mode) -> MenuItem ch label $
modify $ case mode of
Nothing -> edit
Just m -> progMode.editPlayerStateL.epsMode .~ m)
editPlayerMenu = Menu "EDIT PLAYER" () $ map
(\(ch, label, action) -> MenuItem ch label $ modify action)
-- key, label, value
[ ( '1', "Edit number", Just EPNumber )
, ( '2', "Edit name", Just EPName )
, ( '3', "Edit position", Just EPPosition )
, ( '4', "Edit YTD stats", Just EPYtd )
, ( '5', "Edit lifetime stats", Just EPLifetime )
, ( 'R', "Return to Edit Menu", Nothing )
[ ( 'A', "NUMBER", set EPNumber )
, ( 'B', "NAME", set EPName )
, ( 'C', "POSITION", set EPPosition )
, ( 'D', "ROOKIE FLAG", toggleRookie )
, ( 'E', "ACTIVE FLAG", toggleActive )
, ( 'F', "YTD STATS", set EPYtd )
, ( 'G', "LIFETIME STATS", set EPLifetime )
, ( 'H', "DELETE RECORD", set EPDelete )
, ( 'R', "RETURN TO EDIT MENU", edit )
]
where
set mode = progMode.editPlayerStateL.epsMode .~ mode
toggleRookie = editSelectedPlayer $ pRookie %~ not
toggleActive = editSelectedPlayer $ pActive %~ not
-- | The 'Player' YTD stats edit menu
editPlayerYtdMenu :: Menu ()
editPlayerYtdMenu = editMenu
"*** EDIT PLAYER YEAR-TO-DATE ***"
"EDIT PLAYER YEAR-TO-DATE"
-- key, label, value
[ ( '1', "Edit YTD goals", EPYtdGoals )
, ( '2', "Edit YTD assists", EPYtdAssists )
, ( '3', "Edit YTD penalty mins", EPYtdPMin )
, ( 'R', "Return to player edit menu", EPMenu )
[ ( 'A', "ALL YTD STATS", EPYtdGoals True )
, ( 'B', "YTD GOALS", EPYtdGoals False )
, ( 'C', "YTD ASSISTS", EPYtdAssists False )
, ( 'D', "YTD PENALTY MINS", EPYtdPMin )
, ( 'R', "RETURN TO PLAYER EDIT MENU", EPMenu )
]
-- | The 'Player' lifetime stats edit menu
editPlayerLtMenu :: Menu ()
editPlayerLtMenu = editMenu
"*** EDIT PLAYER LIFETIME ***"
"EDIT PLAYER LIFETIME"
-- key, label, value
[ ( '1', "Edit lifetime goals", EPLtGoals )
, ( '2', "Edit lifetime assits", EPLtAssists )
, ( '3', "Edit lifetime penalty mins", EPLtPMin )
, ( 'R', "Return to edit player menu", EPMenu )
[ ( 'A', "ALL LIFETIME STATS", EPLtGoals True )
, ( 'B', "LIFETIME GOALS", EPLtGoals False )
, ( 'C', "LIFETIME ASSITS", EPLtAssists False )
, ( 'D', "LIFETIME PENALTY MINS", EPLtPMin )
, ( 'R', "RETURN TO EDIT PLAYER MENU", EPMenu )
]
editMenu :: String -> [(Char, String, EditPlayerMode)] -> Menu ()

View File

@@ -0,0 +1,64 @@
{- |
mtlstats
Copyright (C) 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.Menu.EditStandings
( editStandingsMenu
, editHomeStandingsMenu
, editAwayStandingsMenu
) where
import Control.Monad.State.Class (modify)
import Mtlstats.Actions
import Mtlstats.Actions.EditStandings
import Mtlstats.Types.Menu
editStandingsMenu :: Menu ()
editStandingsMenu = Menu "EDIT STANDINGS" ()
[ MenuItem 'A' "EDIT HOME STANDINGS" $
modify editHomeStandings
, MenuItem 'B' "EDIT ROAD STANDINGS" $
modify editAwayStandings
, MenuItem 'R' "RETURN TO MAIN MENU" $
modify backHome
]
editHomeStandingsMenu :: Menu ()
editHomeStandingsMenu = subMenu "HOME"
editAwayStandingsMenu :: Menu ()
editAwayStandingsMenu = subMenu "ROAD"
subMenu :: String -> Menu ()
subMenu str = Menu (str ++ " STANDINGS") ()
[ MenuItem 'W' "EDIT WINS" $
modify editWins
, MenuItem 'L' "EDIT LOSSES" $
modify editLosses
, MenuItem 'O' "EDIT OVERTIME GAMES" $
modify editOvertime
, MenuItem 'F' "EDIT GOALS FOR" $
modify editGoalsFor
, MenuItem 'A' "EDIT GOALS AGAINST" $
modify editGoalsAgainst
, MenuItem 'R' "RETURN TO EDIT STANDINGS MENU" $
modify editStandings
]

View File

@@ -1,7 +1,7 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -19,11 +19,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
-}
{-# LANGUAGE LambdaCase #-}
module Mtlstats.Prompt (
-- * Prompt Functions
drawPrompt,
promptHandler,
promptControllerWith,
promptController,
@@ -31,68 +28,73 @@ module Mtlstats.Prompt (
ucStrPrompt,
namePrompt,
numPrompt,
numPromptRange,
numPromptWithFallback,
dbNamePrompt,
selectPrompt,
-- * Individual prompts
getDBPrompt,
newSeasonPrompt,
playerNumPrompt,
playerNamePrompt,
playerPosPrompt,
goalieNumPrompt,
goalieNamePrompt,
selectPlayerPrompt,
selectActivePlayerPrompt,
selectGoaliePrompt,
selectActiveGoaliePrompt,
selectPositionPrompt,
playerToEditPrompt
) where
import Brick.Types (BrickEvent (VtyEvent), Widget)
import Brick.Widgets.Core (hBox, str, vBox)
import Brick.Widgets.Edit (editContentsL, renderEditor)
import Control.Monad (when)
import Control.Monad.Extra (whenJust)
import Control.Monad.Trans.State (gets, modify)
import Data.Char (isDigit, toUpper)
import Data.Foldable (forM_)
import Lens.Micro ((^.), (&), (.~), (?~), (%~))
import Lens.Micro.Extras (view)
import Control.Monad.State.Class (gets, modify)
import Data.Char (isAlphaNum, isDigit, toUpper)
import Data.Text.Zipper (deletePrevChar, insertChar)
import Graphics.Vty.Input.Events
( Event (EvKey)
, Key (KBS, KChar, KEnter, KFun)
)
import Lens.Micro ((^.), (&), (.~), (?~), (%~), to)
import Lens.Micro.Mtl ((%=), use)
import Text.Read (readMaybe)
import qualified UI.NCurses as C
import Mtlstats.Actions
import Mtlstats.Config
import Mtlstats.Helpers.Position
import Mtlstats.Types
import Mtlstats.Util
-- | Draws the prompt to the screen
drawPrompt :: Prompt -> ProgState -> C.Update C.CursorMode
drawPrompt p s = do
promptDrawer p s
return C.CursorVisible
-- | Event handler for a prompt
promptHandler :: Prompt -> C.Event -> Action ()
promptHandler p (C.EventCharacter '\n') = do
val <- gets $ view inputBuffer
modify $ inputBuffer .~ ""
promptHandler :: Prompt -> Handler ()
promptHandler p (VtyEvent (EvKey KEnter [])) = do
val <- use $ editorW.to userText
editorW %= clearEditor
promptAction p val
promptHandler p (C.EventCharacter c) =
modify $ inputBuffer %~ promptProcessChar p c
promptHandler _ (C.EventSpecialKey C.KeyBackspace) =
modify removeChar
promptHandler p (C.EventSpecialKey k) =
promptSpecialKey p k
promptHandler p (VtyEvent (EvKey (KChar c) [])) =
editorW %= promptProcessChar p c
promptHandler _ (VtyEvent (EvKey KBS [])) =
editorW.editContentsL %= deletePrevChar
promptHandler p (VtyEvent (EvKey k m)) =
promptSpecialKey p k m
promptHandler _ _ = return ()
-- | Builds a controller out of a prompt with a header
promptControllerWith
:: (ProgState -> C.Update ())
:: (ProgState -> Widget () -> Widget ())
-- ^ The header
-> Prompt
-- ^ The prompt to use
-> Controller
-- ^ The resulting controller
promptControllerWith header prompt = Controller
{ drawController = \s -> do
header s
drawPrompt prompt s
, handleController = \e -> do
promptHandler prompt e
return True
{ drawController = \s -> header s $ drawPrompt prompt s
, handleController = promptHandler prompt
}
-- | Builds a controller out of a prompt
@@ -101,7 +103,7 @@ promptController
-- ^ The prompt to use
-> Controller
-- ^ The resulting controller
promptController = promptControllerWith (const $ return ())
promptController = promptControllerWith $ const id
-- | Builds a string prompt
strPrompt
@@ -111,10 +113,10 @@ strPrompt
-- ^ The callback function for the result
-> Prompt
strPrompt pStr act = Prompt
{ promptDrawer = drawSimplePrompt pStr
, promptProcessChar = \ch -> (++ [ch])
{ drawPrompt = drawSimplePrompt pStr
, promptProcessChar = \ch -> editContentsL %~ insertChar ch
, promptAction = act
, promptSpecialKey = const $ return ()
, promptSpecialKey = \_ _ -> return ()
}
-- | Creates an upper case string prompt
@@ -125,7 +127,7 @@ ucStrPrompt
-- ^ The callback function for the result
-> Prompt
ucStrPrompt pStr act = (strPrompt pStr act)
{ promptProcessChar = \ch -> (++ [toUpper ch]) }
{ promptProcessChar = \ch -> editContentsL %~ insertChar (toUpper ch) }
-- | Creates a prompt which forces capitalization of input to
-- accomodate a player or goalie name
@@ -145,30 +147,85 @@ numPrompt
-> (Int -> Action ())
-- ^ The callback function for the result
-> Prompt
numPrompt pStr act = Prompt
{ promptDrawer = drawSimplePrompt pStr
, promptProcessChar = \ch str -> if isDigit ch
then str ++ [ch]
else str
, promptAction = \inStr -> forM_ (readMaybe inStr) act
, promptSpecialKey = const $ return ()
numPrompt pStr = numPromptWithFallback pStr $ return ()
-- | Builds a numberic prompt with a range
numPromptRange
:: Int
-- ^ The minimum value
-> Int
-- ^ The maximum value
-> String
-- ^ The prompt string
-> (Int -> Action ())
-- ^ The callback function for the result
-> Prompt
numPromptRange nMin nMax pStr callback = numPrompt pStr $ \n ->
when (n >= nMin && n <= nMax) $ callback n
-- | Builds a numeric prompt with a fallback action
numPromptWithFallback
:: String
-- ^ The prompt string
-> Action ()
-- ^ The action to call on invalid (or blank) input
-> (Int -> Action ())
-- ^ The callback function for the result
-> Prompt
numPromptWithFallback pStr fallback act = Prompt
{ drawPrompt = drawSimplePrompt pStr
, promptProcessChar = \ch existing -> if isDigit ch
then existing & editContentsL %~ insertChar ch
else existing
, promptAction = maybe fallback act . readMaybe
, promptSpecialKey = \_ _ -> return ()
}
-- | Prompts for a database name
dbNamePrompt
:: String
-- ^ The prompt string
-> (String -> Action ())
-- ^ The callback to pass the result to
-> Prompt
dbNamePrompt pStr act = (strPrompt pStr act)
{ promptProcessChar = \ch -> if isAlphaNum ch || ch == '-'
then editContentsL %~ insertChar (toUpper ch)
else id
}
-- | Prompts the user for a filename to save a backup of the database
-- to
newSeasonPrompt :: Prompt
newSeasonPrompt = dbNamePrompt "Filename for new season: " $ \fn ->
if null fn
then modify backHome
else do
saveDatabase
modify
$ (dbName .~ fn)
. (progMode .~ NewSeason True)
-- | Builds a selection prompt
selectPrompt :: SelectParams a -> Prompt
selectPrompt params = Prompt
{ promptDrawer = \s -> do
let sStr = s^.inputBuffer
C.drawString $ spPrompt params ++ sStr
(row, col) <- C.cursorPosition
C.drawString $ "\n\n" ++ spSearchHeader params ++ "\n"
let results = zip [1..maxFunKeys] $ spSearch params sStr (s^.database)
C.drawString $ unlines $ map
{ drawPrompt = \s -> let
sStr = s^.editorW.to userText
pStr = spPrompt params
results = zip [1..maxFunKeys] $ spSearch params sStr (s^.database)
fmtRes = map
(\(n, (_, x)) -> let
desc = spElemDesc params x
in "F" ++ show n ++ ") " ++ desc)
in str $ "F" ++ show n ++ ") " ++ desc)
results
C.moveCursor row col
in vBox $
[ hBox
[ str pStr
, renderEditor linesToWidget True (s^.editorW)
]
, str " "
, str $ spSearchHeader params
] ++ fmtRes
, promptProcessChar = spProcessChar params
, promptAction = \sStr -> if null sStr
then spCallback params Nothing
@@ -177,20 +234,26 @@ selectPrompt params = Prompt
case spSearchExact params sStr db of
Nothing -> spNotFound params sStr
Just n -> spCallback params $ Just n
, promptSpecialKey = \case
C.KeyFunction rawK -> do
sStr <- gets (^.inputBuffer)
db <- gets (^.database)
, promptSpecialKey = \key _ -> case key of
KFun rawK -> do
sStr <- use $ editorW . to userText
db <- use database
let
n = pred $ fromInteger rawK
n = pred rawK
results = spSearch params sStr db
when (n < maxFunKeys) $
whenJust (nth n results) $ \(sel, _) -> do
modify $ inputBuffer .~ ""
editorW %= clearEditor
spCallback params $ Just sel
_ -> return ()
}
-- | Prompts for the database to load
getDBPrompt :: Prompt
getDBPrompt = dbNamePrompt "Season database to load: " $ \fn -> do
modify $ dbName .~ fn
loadDatabase
-- | Prompts for a new player's number
playerNumPrompt :: Prompt
playerNumPrompt = numPrompt "Player number: " $
@@ -203,7 +266,7 @@ playerNamePrompt = namePrompt "Player name: " $
-- | Prompts for a new player's position
playerPosPrompt :: Prompt
playerPosPrompt = ucStrPrompt "Player position: " $
playerPosPrompt = selectPositionPrompt "Player position: " $
modify . (progMode.createPlayerStateL.cpsPosition .~)
-- | Prompts tor the goalie's number
@@ -216,18 +279,21 @@ goalieNamePrompt :: Prompt
goalieNamePrompt = namePrompt "Goalie name: " $
modify . (progMode.createGoalieStateL.cgsName .~)
-- | Selects a player (creating one if necessary)
selectPlayerPrompt
:: String
-- | Selects a player using a specified search function (creating the
-- player if necessary)
selectPlayerPromptWith
:: (String -> [Player] -> [(Int, Player)])
-- ^ The search function
-> String
-- ^ The prompt string
-> (Maybe Int -> Action ())
-- ^ The callback to run (takes the index number of the payer as
-- input)
-> Prompt
selectPlayerPrompt pStr callback = selectPrompt SelectParams
selectPlayerPromptWith sFunc pStr callback = selectPrompt SelectParams
{ spPrompt = pStr
, spSearchHeader = "Player select:"
, spSearch = \sStr db -> playerSearch sStr (db^.dbPlayers)
, spSearch = \sStr db -> sFunc sStr (db^.dbPlayers)
, spSearchExact = \sStr db -> fst <$> playerSearchExact sStr (db^.dbPlayers)
, spElemDesc = playerSummary
, spProcessChar = capitalizeName
@@ -245,18 +311,41 @@ selectPlayerPrompt pStr callback = selectPrompt SelectParams
modify $ progMode .~ CreatePlayer cps
}
-- | Selects a goalie (creating one if necessary)
selectGoaliePrompt
-- | 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 = selectPlayerPromptWith playerSearch
-- | Selects an active player (creating one if necessary)
selectActivePlayerPrompt
:: String
-- ^ The prompt string
-> (Maybe Int -> Action ())
-- ^ The callback to run (takes the index number of the payer as
-- input)
-> Prompt
selectActivePlayerPrompt = selectPlayerPromptWith activePlayerSearch
-- | Selects a goalie with a specified search criteria (creating the
-- goalie if necessary)
selectGoaliePromptWith
:: (String -> [Goalie] -> [(Int, Goalie)])
-- ^ The search criteria
-> String
-- ^ The prompt string
-> (Maybe Int -> Action ())
-- ^ The callback to run (takes the index number of the goalie as
-- input)
-> Prompt
selectGoaliePrompt pStr callback = selectPrompt SelectParams
selectGoaliePromptWith criteria pStr callback = selectPrompt SelectParams
{ spPrompt = pStr
, spSearchHeader = "Goalie select:"
, spSearch = \sStr db -> goalieSearch sStr (db^.dbGoalies)
, spSearch = \sStr db -> criteria sStr (db^.dbGoalies)
, spSearchExact = \sStr db -> fst <$> goalieSearchExact sStr (db^.dbGoalies)
, spElemDesc = goalieSummary
, spProcessChar = capitalizeName
@@ -274,9 +363,50 @@ selectGoaliePrompt pStr callback = selectPrompt SelectParams
modify $ progMode .~ CreateGoalie cgs
}
-- | Selects a goalie (creating one if necessary)
selectGoaliePrompt
:: String
-- ^ The prompt string
-> (Maybe Int -> Action ())
-- ^ The callback to run (takes the index number of the goalie as
-- input)
-> Prompt
selectGoaliePrompt = selectGoaliePromptWith goalieSearch
-- | Selects an active goalie (creating one if necessary)
selectActiveGoaliePrompt
:: String
-- ^ The prompt string
-> (Maybe Int -> Action ())
-- ^ The callback to run (takes the index number of the goalie as
-- input)
-> Prompt
selectActiveGoaliePrompt = selectGoaliePromptWith activeGoalieSearch
-- | Selects (or creates) a player position
selectPositionPrompt
:: String
-- ^ The 'Prompt' string
-> (String -> Action ())
-- ^ The action to perform when a value is entered
-> Prompt
selectPositionPrompt pStr callback = selectPrompt SelectParams
{ spPrompt = pStr
, spSearchHeader = "Positions:"
, spSearch = posSearch
, spSearchExact = posSearchExact
, spElemDesc = id
, spProcessChar = \c -> editContentsL %~ insertChar (toUpper c)
, spCallback = posCallback callback
, spNotFound = callback
}
playerToEditPrompt :: Prompt
playerToEditPrompt = selectPlayerPrompt "Player to edit: " $
modify . (progMode.editPlayerStateL.epsSelectedPlayer .~)
drawSimplePrompt :: String -> ProgState -> C.Update ()
drawSimplePrompt pStr s = C.drawString $ pStr ++ s^.inputBuffer
drawSimplePrompt :: String -> Renderer
drawSimplePrompt pStr s = hBox
[ str pStr
, renderEditor linesToWidget True (s^.editorW)
]

View File

@@ -1,7 +1,7 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -26,21 +26,23 @@ module Mtlstats.Prompt.EditGoalie
, editGoalieYtdGamesPrompt
, editGoalieYtdMinsPrompt
, editGoalieYtdGoalsPrompt
, editGoalieYtdShutoutsPrompt
, editGoalieYtdWinsPrompt
, editGoalieYtdLossesPrompt
, editGoalieYtdTiesPrompt
, editGoalieLtGamesPrompt
, editGoalieLtMinsPrompt
, editGoalieLtGoalsPrompt
, editGoalieLtShutoutsPrompt
, editGoalieLtWinsPrompt
, editGoalieLtLossesPrompt
, editGoalieLtTiesPrompt
) where
import Control.Monad.Trans.State (modify)
import Control.Monad.State.Class (modify)
import Lens.Micro ((.~))
import Mtlstats.Actions.EditGoalie
import Mtlstats.Actions
import Mtlstats.Prompt
import Mtlstats.Types
@@ -50,71 +52,236 @@ goalieToEditPrompt = selectGoaliePrompt "Goalie to edit: " $
modify . (progMode.editGoalieStateL.egsSelectedGoalie .~)
-- | Prompt to edit a goalie's number
editGoalieNumberPrompt :: Prompt
editGoalieNumberPrompt = numPrompt "Goalie number: " $
modify . editGoalieNumber
editGoalieNumberPrompt
:: Action ()
-- ^ Action to perform on completion
-> Prompt
editGoalieNumberPrompt = editNum "Goalie number: " EGMenu
(gNumber .~)
-- | Prompt to edit a goalie's name
editGoalieNamePrompt :: Prompt
editGoalieNamePrompt = strPrompt "Goalie name: " $
modify . editGoalieName
editGoalieNamePrompt
:: Action ()
-- ^ Action to perform on completion
-> Prompt
editGoalieNamePrompt cb = namePrompt "Goalie name: " $ \name -> do
if null name
then goto EGMenu
else doEdit EGMenu $ gName .~ name
cb
-- | Prompt to edit a goalie's YTD games played
editGoalieYtdGamesPrompt :: Prompt
editGoalieYtdGamesPrompt = numPrompt "Year-to-date games played: " $
modify . editGoalieYtdGames
editGoalieYtdGamesPrompt
:: Bool
-- ^ Indicates whether or not we're in batch mode
-> Action ()
-- ^ Action to perform on completion
-> Prompt
editGoalieYtdGamesPrompt batchMode cb =
editNum "Year-to-date games played: " mode
(gYtd.gsGames .~) cb'
where
(mode, cb') = if batchMode
then (EGYtdMins True, return ())
else (EGYtd, cb)
-- | Prompt to edit a goalie's YTD minutes played
editGoalieYtdMinsPrompt :: Prompt
editGoalieYtdMinsPrompt = numPrompt "Year-to-date minutes played: " $
modify . editGoalieYtdMins
editGoalieYtdMinsPrompt
:: Bool
-- ^ Indicates whether or not we're in batch mode
-> Action ()
-- ^ Action to perform on completion
-> Prompt
editGoalieYtdMinsPrompt batchMode cb =
editNum "Year-to-date minutes played: " mode
(gYtd.gsMinsPlayed .~) cb'
where
(mode, cb') = if batchMode
then (EGYtdGoals True, return ())
else (EGYtd, cb)
-- | Prompt to edit a goalie's YTD goales allowed
editGoalieYtdGoalsPrompt :: Prompt
editGoalieYtdGoalsPrompt = numPrompt "Year-to-date goals allowed: " $
modify . editGoalieYtdGoals
editGoalieYtdGoalsPrompt
:: Bool
-- ^ Indicates whether or not we're in batch mode
-> Action ()
-- ^ Action to perform on completion
-> Prompt
editGoalieYtdGoalsPrompt batchMode cb =
editNum "Year-to-date goals allowed: " mode
(gYtd.gsGoalsAllowed .~) cb'
where
(mode, cb') = if batchMode
then (EGYtdShutouts True, return ())
else (EGYtd, cb)
-- | Prompt to edit a goalie's YTD shutouts
editGoalieYtdShutoutsPrompt
:: Bool
-- ^ Indicates whether or not we're in batch mode
-> Action ()
-- ^ Action to perform on completion
-> Prompt
editGoalieYtdShutoutsPrompt batchMode cb =
editNum "Year-to-date shutouts: " mode
(gYtd.gsShutouts .~) cb'
where
(mode, cb') = if batchMode
then (EGYtdWins True, return ())
else (EGYtd, cb)
-- | Prompt to edit a goalie's YTD wins
editGoalieYtdWinsPrompt :: Prompt
editGoalieYtdWinsPrompt = numPrompt "Year-to-date wins: " $
modify . editGoalieYtdWins
editGoalieYtdWinsPrompt
:: Bool
-- ^ Indicates whether or not we're in batch mode
-> Action ()
-- ^ Action to perform on completion
-> Prompt
editGoalieYtdWinsPrompt batchMode cb =
editNum "Year-to-date wins: " mode
(gYtd.gsWins .~) cb'
where
(mode, cb') = if batchMode
then (EGYtdLosses True, return ())
else (EGYtd, cb)
-- | Prompt to edit a goalie's YTD losses
editGoalieYtdLossesPrompt :: Prompt
editGoalieYtdLossesPrompt = numPrompt "Year-to-date losses: " $
modify . editGoalieYtdLosses
editGoalieYtdLossesPrompt
:: Bool
-- ^ Indicates whether or not we're in batch mode
-> Action ()
-- ^ Action to perform on completion
-> Prompt
editGoalieYtdLossesPrompt batchMode cb =
editNum "Year-to-date losses: " mode
(gYtd.gsLosses .~) cb'
where
(mode, cb') = if batchMode
then (EGYtdTies, return ())
else (EGYtd, cb)
-- | Prompt to edit a goalie's YTD ties
editGoalieYtdTiesPrompt :: Prompt
editGoalieYtdTiesPrompt = numPrompt "Year-to-date ties: " $
modify . editGoalieYtdTies
editGoalieYtdTiesPrompt
:: Action ()
-- ^ Action to perform on completion
-> Prompt
editGoalieYtdTiesPrompt = editNum "Year-to-date ties: " EGYtd
(gYtd.gsTies .~)
-- | Prompt to edit a goalie's lifetime games played
editGoalieLtGamesPrompt :: Prompt
editGoalieLtGamesPrompt = numPrompt "Lifetime games played: " $
modify . editGoalieLtGames
editGoalieLtGamesPrompt
:: Bool
-- ^ Indicates whether or not we're in batch mode
-> Action ()
-- ^ Action to perform on completion
-> Prompt
editGoalieLtGamesPrompt batchMode cb =
editNum "Lifetime games played: " mode
(gLifetime.gsGames .~) cb'
where
(mode, cb') = if batchMode
then (EGLtMins True, return ())
else (EGLifetime, cb)
-- | Prompt to edit a goalie's lifetime minutes played
editGoalieLtMinsPrompt :: Prompt
editGoalieLtMinsPrompt = numPrompt "Lifetime minutes played: " $
modify . editGoalieLtMins
editGoalieLtMinsPrompt
:: Bool
-- ^ Indicates whether or not we're in batch mode
-> Action ()
-- ^ Action to perform on completion
-> Prompt
editGoalieLtMinsPrompt batchMode cb =
editNum "Lifetime minutes played: " mode
(gLifetime.gsMinsPlayed .~) cb'
where
(mode, cb') = if batchMode
then (EGLtGoals True, return ())
else (EGLifetime, cb)
-- | Prompt to edit a goalie's lifetime goals allowed
editGoalieLtGoalsPrompt :: Prompt
editGoalieLtGoalsPrompt = numPrompt "Lifetime goals allowed: " $
modify . editGoalieLtGoals
editGoalieLtGoalsPrompt
:: Bool
-- ^ Indicates whether or not we're in batch mode
-> Action ()
-- ^ Action to perform on completion
-> Prompt
editGoalieLtGoalsPrompt batchMode cb =
editNum "Lifetime goals allowed: " mode
(gLifetime.gsGoalsAllowed .~) cb'
where
(mode, cb') = if batchMode
then (EGLtShutouts True, return ())
else (EGLifetime, cb)
-- | Prompt to edit a goalie's lifetime shutouts
editGoalieLtShutoutsPrompt
:: Bool
-- ^ Indicates whether or not we're in batch mode
-> Action ()
-- ^ Action to perform on completion
-> Prompt
editGoalieLtShutoutsPrompt batchMode cb =
editNum "Lifetime shutouts: " mode
(gLifetime.gsShutouts .~) cb'
where
(mode, cb') = if batchMode
then (EGLtWins True, return ())
else (EGLifetime, cb)
-- | Prompt to edit a goalie's lifetime wins
editGoalieLtWinsPrompt :: Prompt
editGoalieLtWinsPrompt = numPrompt "Lifetime wins: " $
modify . editGoalieLtWins
editGoalieLtWinsPrompt
:: Bool
-- ^ Indicates whether or not we're in batch mode
-> Action ()
-- ^ Action to perform on completion
-> Prompt
editGoalieLtWinsPrompt batchMode cb =
editNum "Lifetime wins: " mode
(gLifetime.gsWins .~) cb'
where
(mode, cb') = if batchMode
then (EGLtLosses True, return ())
else (EGLifetime, cb)
-- | Prompt to edit a goalie's lifetime losses
editGoalieLtLossesPrompt :: Prompt
editGoalieLtLossesPrompt = numPrompt "Lifetime losses: " $
modify . editGoalieLtLosses
editGoalieLtLossesPrompt
:: Bool
-- ^ Indicates whether or not we're in batch mode
-> Action ()
-- ^ Action to perform on completion
-> Prompt
editGoalieLtLossesPrompt batchMode cb =
editNum "Lifetime losses: " mode
(gLifetime.gsLosses .~) cb'
where
(mode, cb') = if batchMode
then (EGLtTies, return ())
else (EGLifetime, cb)
-- | Prompt to edit a goalie's lifetime ties
editGoalieLtTiesPrompt :: Prompt
editGoalieLtTiesPrompt = numPrompt "Lifetime ties: " $
modify . editGoalieLtTies
editGoalieLtTiesPrompt
:: Action ()
-- ^ Action to perform on completion
-> Prompt
editGoalieLtTiesPrompt = editNum "Lifetime ties: " EGLifetime
(gLifetime.gsTies .~)
editNum
:: String
-> EditGoalieMode
-> (Int -> Goalie -> Goalie)
-> Action ()
-> Prompt
editNum pStr mode f cb = numPromptWithFallback pStr
(goto mode >> cb)
(\num -> do
doEdit mode $ f num
cb)
doEdit :: EditGoalieMode -> (Goalie -> Goalie) -> Action ()
doEdit mode f = do
modify $ editSelectedGoalie f
goto mode
goto :: EditGoalieMode -> Action ()
goto = modify . (progMode.editGoalieStateL.egsMode .~)

View File

@@ -1,7 +1,7 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -31,62 +31,131 @@ module Mtlstats.Prompt.EditPlayer
, editPlayerLtPMinPrompt
) where
import Control.Monad.Extra (whenJustM)
import Control.Monad.Trans.State (gets, modify)
import Lens.Micro ((^.), (.~), (%~))
import Control.Monad.State.Class (modify)
import Lens.Micro ((.~))
import Mtlstats.Actions
import Mtlstats.Prompt
import Mtlstats.Types
import Mtlstats.Util
-- | Prompt to edit a player's number
editPlayerNumPrompt :: Prompt
editPlayerNumPrompt = numPrompt "Player number: " $
editPlayer . (pNumber .~)
editPlayerNumPrompt
:: Action ()
-- ^ The action to be performed upon completion
-> Prompt
editPlayerNumPrompt = editNum "Player number: " EPMenu
(pNumber .~)
-- | Prompt to edit a player's name
editPlayerNamePrompt :: Prompt
editPlayerNamePrompt = strPrompt "Player name: " $
editPlayer . (pName .~)
editPlayerNamePrompt
:: Action ()
-- ^ The action to be performed upon completion
-> Prompt
editPlayerNamePrompt callback = namePrompt "Player name: " $ \name -> do
if null name
then goto EPMenu
else doEdit EPMenu $ pName .~ name
callback
-- | Prompt to edit a player's position
editPlayerPosPrompt :: Prompt
editPlayerPosPrompt = ucStrPrompt "Player position: " $
editPlayer . (pPosition .~)
editPlayerPosPrompt
:: Action ()
-- ^ The action to be performed upon completion
-> Prompt
editPlayerPosPrompt callback = selectPositionPrompt "Player position: " $ \pos -> do
if null pos
then goto EPMenu
else doEdit EPMenu $ pPosition .~ pos
callback
-- | Prompt to edit a player's year-to-date goals
editPlayerYtdGoalsPrompt :: Prompt
editPlayerYtdGoalsPrompt = numPrompt "Year-to-date goals: " $
editPlayer . (pYtd.psGoals .~)
editPlayerYtdGoalsPrompt
:: Bool
-- ^ Indicates wheter or not we're editing in batch mode
-> Action ()
-- ^ The action to be performed upon completion
-> Prompt
editPlayerYtdGoalsPrompt batchMode callback = editNum "Year-to-date goals: " mode
(pYtd.psGoals .~) callback'
where
(mode, callback') = if batchMode
then (EPYtdAssists True, return ())
else (EPYtd, callback)
-- | Prompt to edit a player's year-to-date assists
editPlayerYtdAssistsPrompt :: Prompt
editPlayerYtdAssistsPrompt = numPrompt "Year-to-date assists: " $
editPlayer . (pYtd.psAssists .~)
editPlayerYtdAssistsPrompt
:: Bool
-- ^ Indicates wheter or not we're editing in batch mode
-> Action ()
-- ^ The action to be performed upon completion
-> Prompt
editPlayerYtdAssistsPrompt batchMode callback = editNum "Year-to-date assists: " mode
(pYtd.psAssists .~) callback'
where
(mode, callback') = if batchMode
then (EPYtdPMin, return ())
else (EPYtd, callback)
-- | Prompt to edit a player's year-to-date penalty minutes
editPlayerYtdPMinPrompt :: Prompt
editPlayerYtdPMinPrompt = numPrompt "Year-to-date penalty minutes: " $
editPlayer . (pYtd.psPMin .~)
editPlayerYtdPMinPrompt
:: Action ()
-- ^ The action to be performed upon completion
-> Prompt
editPlayerYtdPMinPrompt = editNum "Year-to-date penalty minutes: " EPYtd
(pYtd.psPMin .~)
-- | Prompt to edit a player's lifetime goals
editPlayerLtGoalsPrompt :: Prompt
editPlayerLtGoalsPrompt = numPrompt "Lifetime goals: " $
editPlayer . (pLifetime.psGoals .~)
editPlayerLtGoalsPrompt
:: Bool
-- ^ Indicates wheter or not we're editing in batch mode
-> Action ()
-- ^ The action to be performed upon completion
-> Prompt
editPlayerLtGoalsPrompt batchMode callback = editNum "Lifetime goals: " mode
(pLifetime.psGoals .~) callback'
where
(mode, callback') = if batchMode
then (EPLtAssists True, return ())
else (EPLifetime, callback)
-- | Prompt to edit a player's lifetime assists
editPlayerLtAssistsPrompt :: Prompt
editPlayerLtAssistsPrompt = numPrompt "Lifetime assists: " $
editPlayer . (pLifetime.psAssists .~)
editPlayerLtAssistsPrompt
:: Bool
-- ^ Indicates wheter or not we're editing in batch mode
-> Action ()
-- ^ The action to be performed upon completion
-> Prompt
editPlayerLtAssistsPrompt batchMode callback = editNum "Lifetime assists: " mode
(pLifetime.psAssists .~) callback'
where
(mode, callback') = if batchMode
then (EPLtPMin, return ())
else (EPLifetime, callback)
-- | Prompt to edit a player's lifetime penalty minutes
editPlayerLtPMinPrompt :: Prompt
editPlayerLtPMinPrompt = numPrompt "Lifetime penalty minutes: " $
editPlayer . (pLifetime.psPMin .~)
editPlayerLtPMinPrompt
:: Action ()
-- ^ The action to be performed upon completion
-> Prompt
editPlayerLtPMinPrompt = editNum "Lifetime penalty minutes: " EPLifetime
(pLifetime.psPMin .~)
editPlayer :: (Player -> Player) -> Action ()
editPlayer f =
whenJustM (gets (^.progMode.editPlayerStateL.epsSelectedPlayer)) $ \pid ->
modify
$ (database.dbPlayers %~ modifyNth pid f)
. (progMode.editPlayerStateL.epsMode .~ EPMenu)
editNum
:: String
-> EditPlayerMode
-> (Int -> Player -> Player)
-> Action ()
-> Prompt
editNum pStr mode f callback = numPromptWithFallback pStr
(goto mode >> callback)
(\num -> do
doEdit mode $ f num
callback)
doEdit :: EditPlayerMode -> (Player -> Player) -> Action ()
doEdit mode f = do
modify $ editSelectedPlayer f
goto mode
goto :: EditPlayerMode -> Action ()
goto = modify . (progMode.editPlayerStateL.epsMode .~)

View File

@@ -0,0 +1,89 @@
{- |
mtlstats
Copyright (C) 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.Prompt.EditStandings
( editHomeWinsPrompt
, editHomeLossesPrompt
, editHomeOvertimePrompt
, editHomeGoalsForPrompt
, editHomeGoalsAgainstPrompt
, editAwayWinsPrompt
, editAwayLossesPrompt
, editAwayOvertimePrompt
, editAwayGoalsForPrompt
, editAwayGoalsAgainstPrompt
) where
import Control.Monad.State.Class (modify)
import Lens.Micro ((.~), (%~))
import Mtlstats.Prompt
import Mtlstats.Types
editHomeWinsPrompt :: Prompt
editHomeWinsPrompt =
mkPrompt "Home wins: " (dbHomeGameStats.gmsWins .~)
editHomeLossesPrompt :: Prompt
editHomeLossesPrompt =
mkPrompt "Home losses: " (dbHomeGameStats.gmsLosses .~)
editHomeOvertimePrompt :: Prompt
editHomeOvertimePrompt =
mkPrompt "Home overtime games: " (dbHomeGameStats.gmsOvertime .~)
editHomeGoalsForPrompt :: Prompt
editHomeGoalsForPrompt =
mkPrompt "Home goals for: " (dbHomeGameStats.gmsGoalsFor .~)
editHomeGoalsAgainstPrompt :: Prompt
editHomeGoalsAgainstPrompt =
mkPrompt "Home goals against: " (dbHomeGameStats.gmsGoalsAgainst .~)
editAwayWinsPrompt :: Prompt
editAwayWinsPrompt =
mkPrompt "Road wins: " (dbAwayGameStats.gmsWins .~)
editAwayLossesPrompt :: Prompt
editAwayLossesPrompt =
mkPrompt "Road losses: " (dbAwayGameStats.gmsLosses .~)
editAwayOvertimePrompt :: Prompt
editAwayOvertimePrompt =
mkPrompt "Road overtime games: " (dbAwayGameStats.gmsOvertime .~)
editAwayGoalsForPrompt :: Prompt
editAwayGoalsForPrompt =
mkPrompt "Road goals for: " (dbAwayGameStats.gmsGoalsFor .~)
editAwayGoalsAgainstPrompt :: Prompt
editAwayGoalsAgainstPrompt =
mkPrompt "Road goals against: " (dbAwayGameStats.gmsGoalsAgainst .~)
mkPrompt :: String -> (Int -> Database -> Database) -> Prompt
mkPrompt pStr f = numPromptWithFallback pStr
(modify subMenu)
(\n -> modify
$ (database %~ f n)
. subMenu)
subMenu :: ProgState -> ProgState
subMenu = progMode.editStandingsModeL.esmSubModeL .~ ESMSubMenu

View File

@@ -1,7 +1,7 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -23,6 +23,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
module Mtlstats.Prompt.NewGame
( gameYearPrompt
, gameMonthPrompt
, gameDayPrompt
, otherTeamPrompt
, homeScorePrompt
@@ -34,7 +35,7 @@ module Mtlstats.Prompt.NewGame
) where
import Control.Monad (when)
import Control.Monad.Trans.State (gets, modify)
import Control.Monad.State.Class (gets, modify)
import Lens.Micro ((^.), (.~), (?~), (%~))
import Mtlstats.Actions.NewGame
@@ -48,6 +49,11 @@ gameYearPrompt :: Prompt
gameYearPrompt = numPrompt "Game year: " $
modify . (progMode.gameStateL.gameYear ?~)
-- | Prompts for the game month
gameMonthPrompt :: Prompt
gameMonthPrompt = numPromptRange 1 12 "Game month: " $
modify . (progMode.gameStateL.gameMonth ?~)
-- | Prompts for the day of the month the game took place
gameDayPrompt :: Prompt
gameDayPrompt = numPrompt "Day of month: " $
@@ -76,7 +82,7 @@ recordGoalPrompt
-> Int
-- ^ The goal number
-> Prompt
recordGoalPrompt game goal = selectPlayerPrompt
recordGoalPrompt game goal = selectActivePlayerPrompt
( "*** GAME " ++ padNum 2 game ++ " ***\n"
++ "Who scored goal number " ++ show goal ++ "? "
) $ modify . (progMode.gameStateL.goalBy .~)
@@ -90,7 +96,7 @@ recordAssistPrompt
-> Int
-- ^ The assist number
-> Prompt
recordAssistPrompt game goal assist = selectPlayerPrompt
recordAssistPrompt game goal assist = selectActivePlayerPrompt
( "*** GAME " ++ padNum 2 game ++ " ***\n"
++ "Goal: " ++ show goal ++ "\n"
++ "Assist #" ++ show assist ++ ": "
@@ -104,7 +110,7 @@ recordAssistPrompt game goal assist = selectPlayerPrompt
-- | Prompts for the player to assign penalty minutes to
pMinPlayerPrompt :: Prompt
pMinPlayerPrompt = selectPlayerPrompt
pMinPlayerPrompt = selectActivePlayerPrompt
"Assign penalty minutes to: " $
\case
Nothing -> modify $ progMode.gameStateL.gamePMinsRecorded .~ True

View File

@@ -1,7 +1,7 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -27,7 +27,7 @@ module Mtlstats.Prompt.NewGame.GoalieInput
, goalsAllowedPrompt
) where
import Control.Monad.Trans.State (modify)
import Control.Monad.State.Class (modify)
import Lens.Micro ((?~))
import Mtlstats.Actions.NewGame.GoalieInput
@@ -36,7 +36,8 @@ import Mtlstats.Types
-- | Prompts for a goalie who played in the game
selectGameGoaliePrompt :: Prompt
selectGameGoaliePrompt = selectGoaliePrompt "Which goalie played this game: " $
selectGameGoaliePrompt = selectActiveGoaliePrompt
"Which goalie played this game: " $
\case
Nothing -> modify finishGoalieEntry
Just n -> modify $ progMode.gameStateL.gameSelectedGoalie ?~ n

View File

@@ -1,7 +1,7 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -19,7 +19,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
-}
module Mtlstats.Report (report, gameDate) where
module Mtlstats.Report (displayReport, exportReport, gameDate) where
import Data.List (sortOn)
import qualified Data.Map as M
@@ -29,24 +29,42 @@ import Lens.Micro ((^.))
import Mtlstats.Config
import Mtlstats.Format
import Mtlstats.Helpers.Goalie
import Mtlstats.Helpers.Player
import Mtlstats.Types
import Mtlstats.Util
-- | Generates the report
report
-- | Generates the report displayed on screen
displayReport
:: Int
-- ^ The number of columns for the report
-> ProgState
-- ^ The program state
-> [String]
displayReport width s
= report width s
++ [""]
++ lifetimeStatsReport width s
-- | Generates the report to be exported to file
exportReport
:: Int
-- ^ The number of columns in the report
-> ProgState
-- ^ The program state
-> String
exportReport width s
= unlines (report width s)
++ "\f"
++ unlines (lifetimeStatsReport width s)
report :: Int -> ProgState -> [String]
report width s
= standingsReport width s
++ [""]
++ gameStatsReport width s
++ [""]
++ yearToDateStatsReport width s
++ [""]
++ lifetimeStatsReport width s
standingsReport :: Int -> ProgState -> [String]
standingsReport width s = fromMaybe [] $ do
@@ -115,7 +133,7 @@ gameStatsReport width s = let
gs = s^.progMode.gameStateL
db = s^.database
playerStats = mapMaybe
playerStats = sortPlayers $ mapMaybe
(\(pid, stats) -> do
p <- nth pid $ db^.dbPlayers
Just (p, stats))
@@ -129,7 +147,7 @@ gameStatsReport width s = let
criteria (_, ps) = psPoints ps > 0
in filteredPlayerReport width "GAME" criteria True playerStats
in filteredPlayerReport width "GAME" criteria True False playerStats
++ [""]
++ gameGoalieReport width goalieStats
@@ -137,7 +155,7 @@ yearToDateStatsReport :: Int -> ProgState -> [String]
yearToDateStatsReport width s = let
db = s^.database
playerStats = sortOn (Down . psPoints . snd)
playerStats = sortPlayers
$ map (\p -> (p, p^.pYtd))
$ filter playerIsActive
$ db^.dbPlayers
@@ -146,24 +164,24 @@ yearToDateStatsReport width s = let
$ filter goalieIsActive
$ db^.dbGoalies
in playerReport width "YEAR TO DATE" True playerStats
in playerReport width "YEAR TO DATE" True False playerStats
++ [""]
++ goalieReport width True goalieStats
++ goalieReport width True False goalieStats
lifetimeStatsReport :: Int -> ProgState -> [String]
lifetimeStatsReport width s = let
db = s^.database
playerStats = sortOn (Down . psPoints . snd)
playerStats = sortPlayers
$ map (\p -> (p, p^.pLifetime))
$ db^.dbPlayers
goalieStats = map (\g -> (g, g^.gLifetime))
$ db^.dbGoalies
in playerReport width "LIFETIME" False playerStats
in playerReport width "LIFETIME" False True playerStats
++ [""]
++ goalieReport width False goalieStats
++ goalieReport width False True goalieStats
gameDate :: GameState -> String
gameDate gs = fromMaybe "" $ do
@@ -176,6 +194,7 @@ playerReport
:: Int
-> String
-> Bool
-> Bool
-> [(Player, PlayerStats)]
-> [String]
playerReport width label =
@@ -186,11 +205,13 @@ filteredPlayerReport
-> String
-> ((Player, PlayerStats) -> Bool)
-> Bool
-> Bool
-> [(Player, PlayerStats)]
-> [String]
filteredPlayerReport width label criteria showTotals ps = let
tStats = foldl addPlayerStats newPlayerStats $ map snd ps
fps = filter criteria ps
filteredPlayerReport width label criteria showTotals lineNumbers ps = let
tStats = foldl addPlayerStats newPlayerStats $ map snd ps
criteria' = (&&) <$> criteria <*> \(p, _) -> p^.pNumber /= 0
fps = filter criteria' ps
rHeader =
[ centre width (label ++ " STATISTICS")
@@ -216,7 +237,7 @@ filteredPlayerReport width label criteria showTotals ps = let
body = map
(\(p, stats) ->
[ CellText $ show (p^.pNumber) ++ " "
, CellText $ p^.pName
, CellText $ playerName p
] ++ statsCells stats)
fps
@@ -231,8 +252,12 @@ filteredPlayerReport width label criteria showTotals ps = let
then label ++ " TOTALS"
else ""
lnOverlay = if lineNumbers
then "" : [right 2 $ show x | x <- [(1 :: Int)..]]
else repeat ""
table = overlayLast olayText
$ map (centre width)
$ zipWith (\ln line -> overlay ln $ centre width line) lnOverlay
$ complexTable ([right, left] ++ repeat right)
$ tHeader : body ++ if showTotals
then [separator, totals]
@@ -243,15 +268,18 @@ filteredPlayerReport width label criteria showTotals ps = let
goalieReport
:: Int
-> Bool
-> Bool
-> [(Goalie, GoalieStats)]
-> [String]
goalieReport width showTotals goalieData = let
goalieReport width showTotals lineNumbers goalieData = let
olayText = if showTotals
then "GOALTENDING TOTALS"
else ""
goalieData' = sortGoalies goalieData
tData = foldl addGoalieStats newGoalieStats
$ map snd goalieData
$ map snd goalieData'
header =
[ CellText "NO."
@@ -274,9 +302,9 @@ goalieReport width showTotals goalieData = let
body = map
(\(goalie, stats) ->
[ CellText $ show (goalie^.gNumber) ++ " "
, CellText $ goalie^.gName
, CellText $ goalieName goalie
] ++ rowCells stats)
goalieData
goalieData'
separator
= replicate 2 (CellText "")
@@ -284,7 +312,11 @@ goalieReport width showTotals goalieData = let
summary = replicate 2 (CellText "") ++ rowCells tData
in map (centre width)
lnOverlay = if lineNumbers
then "" : [right 2 $ show x | x <- [(1 :: Int)..]]
else repeat ""
in zipWith (\ln line -> overlay ln $ centre width line) lnOverlay
$ overlayLast olayText
$ complexTable ([right, left] ++ repeat right)
$ header : body ++ if showTotals
@@ -293,6 +325,8 @@ goalieReport width showTotals goalieData = let
gameGoalieReport :: Int -> [(Goalie, GoalieStats)] -> [String]
gameGoalieReport width goalieData = let
goalieData' = sortGoalies goalieData
header =
[ CellText "NO."
, CellText "GOALTENDER"
@@ -304,13 +338,21 @@ gameGoalieReport width goalieData = let
body = map
(\(goalie, stats) ->
[ CellText $ show (goalie^.gNumber) ++ " "
, CellText $ goalie^.gName
, CellText $ goalieName goalie
, CellText $ show $ stats^.gsMinsPlayed
, CellText $ show $ stats^.gsGoalsAllowed
, CellText $ showFloating $ gsAverage stats
])
goalieData
goalieData'
in map (centre width)
$ complexTable ([right, left] ++ repeat right)
$ header : body
sortPlayers :: [(Player, PlayerStats)] -> [(Player, PlayerStats)]
sortPlayers = sortOn $ Down . \(p, ps) ->
(psPoints ps, psPoints $ p^.pLifetime)
sortGoalies :: [(Goalie, GoalieStats)] -> [(Goalie, GoalieStats)]
sortGoalies = sortOn $ Down . \(g, gs) ->
(gs^.gsMinsPlayed, g^.gLifetime.gsMinsPlayed)

View File

@@ -1,7 +1,7 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -24,7 +24,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
module Mtlstats.Types (
-- * Types
Controller (..),
Renderer,
Action,
Handler,
ProgState (..),
ProgMode (..),
GameState (..),
@@ -35,6 +37,8 @@ module Mtlstats.Types (
EditPlayerMode (..),
EditGoalieState (..),
EditGoalieMode (..),
EditStandingsMode (..),
ESMSubMode (..),
Database (..),
Player (..),
PlayerStats (..),
@@ -48,14 +52,18 @@ module Mtlstats.Types (
-- ** ProgState Lenses
database,
progMode,
inputBuffer,
scrollOffset,
dbName,
editorW,
scroller,
-- ** ProgMode Lenses
gameStateL,
createPlayerStateL,
createGoalieStateL,
editPlayerStateL,
editGoalieStateL,
editStandingsModeL,
-- ** EditStandingsMode Lenses
esmSubModeL,
-- ** GameState Lenses
gameYear,
gameMonth,
@@ -83,19 +91,25 @@ module Mtlstats.Types (
cpsNumber,
cpsName,
cpsPosition,
cpsRookieFlag,
cpsActiveFlag,
cpsSuccessCallback,
cpsFailureCallback,
-- ** CreateGoalieState Lenses
cgsNumber,
cgsName,
cgsRookieFlag,
cgsActiveFlag,
cgsSuccessCallback,
cgsFailureCallback,
-- ** EditPlayerState Lenses
epsSelectedPlayer,
epsMode,
epsCallback,
-- ** EditGoalieState Lenses
egsSelectedGoalie,
egsMode,
egsCallback,
-- ** Database Lenses
dbPlayers,
dbGoalies,
@@ -106,6 +120,8 @@ module Mtlstats.Types (
pNumber,
pName,
pPosition,
pRookie,
pActive,
pYtd,
pLifetime,
-- ** PlayerStats Lenses
@@ -115,6 +131,8 @@ module Mtlstats.Types (
-- ** Goalie Lenses
gNumber,
gName,
gRookie,
gActive,
gYtd,
gLifetime,
-- ** GoalieStats Lenses
@@ -160,6 +178,7 @@ module Mtlstats.Types (
addGameStats,
-- ** Player Helpers
playerSearch,
activePlayerSearch,
playerSearchExact,
modifyPlayer,
playerSummary,
@@ -169,6 +188,7 @@ module Mtlstats.Types (
addPlayerStats,
-- ** Goalie Helpers
goalieSearch,
activeGoalieSearch,
goalieSearchExact,
goalieSummary,
goalieIsActive,
@@ -177,7 +197,9 @@ module Mtlstats.Types (
gsAverage
) where
import Control.Monad.Trans.State (StateT)
import Brick.Main (ViewportScroll, viewportScroll)
import Brick.Types (BrickEvent, EventM, Widget)
import Brick.Widgets.Edit (Editor, editor)
import Data.Aeson
( FromJSON
, ToJSON
@@ -193,58 +215,69 @@ import Data.Aeson
, (.=)
)
import Data.Char (toUpper)
import Data.List (isInfixOf)
import Data.List (find, isInfixOf)
import qualified Data.Map as M
import Data.Maybe (listToMaybe)
import Graphics.Vty.Input.Events (Key, Modifier)
import Lens.Micro (Lens', lens, (&), (^.), (.~))
import Lens.Micro.TH (makeLenses)
import qualified UI.NCurses as C
import Mtlstats.Config
-- | Controls the program flow
data Controller = Controller
{ drawController :: ProgState -> C.Update C.CursorMode
-- ^ The drawing phase
, handleController :: C.Event -> Action Bool
{ drawController :: Renderer
-- ^ The drawing routine
, handleController :: Handler ()
-- ^ The event handler
}
-- | Renders a view based on a "ProgState"
type Renderer = ProgState -> Widget ()
-- | Action which maintains program state
type Action a = StateT ProgState C.Curses a
type Action a = EventM () ProgState a
-- | Handles an event
type Handler a = BrickEvent () () -> Action a
-- | Represents the program state
data ProgState = ProgState
{ _database :: Database
{ _database :: Database
-- ^ The data to be saved
, _progMode :: ProgMode
, _progMode :: ProgMode
-- ^ The program's mode
, _inputBuffer :: String
-- ^ Buffer for user input
, _scrollOffset :: Int
-- ^ The scrolling offset for the display
, _dbName :: String
-- ^ The name of the database file
, _editorW :: Editor String ()
-- ^ Editor widget
, _scroller :: ViewportScroll ()
-- ^ Scroller for the reports
}
-- | The program mode
data ProgMode
= MainMenu
| NewSeason
= TitleScreen
| MainMenu
| NewSeason Bool
| NewGame GameState
| EditMenu
| CreatePlayer CreatePlayerState
| CreateGoalie CreateGoalieState
| EditPlayer EditPlayerState
| EditGoalie EditGoalieState
| EditStandings EditStandingsMode
instance Show ProgMode where
show MainMenu = "MainMenu"
show NewSeason = "NewSeason"
show (NewGame _) = "NewGame"
show EditMenu = "EditMenu"
show (CreatePlayer _) = "CreatePlayer"
show (CreateGoalie _) = "CreateGoalie"
show (EditPlayer _) = "EditPlayer"
show (EditGoalie _) = "EditGoalie"
show TitleScreen = "TitleScreen"
show MainMenu = "MainMenu"
show (NewSeason _) = "NewSeason"
show (NewGame _) = "NewGame"
show EditMenu = "EditMenu"
show (CreatePlayer _) = "CreatePlayer"
show (CreateGoalie _) = "CreateGoalie"
show (EditPlayer _) = "EditPlayer"
show (EditGoalie _) = "EditGoalie"
show (EditStandings _) = "EditStandings"
-- | The game state
data GameState = GameState
@@ -313,6 +346,10 @@ data CreatePlayerState = CreatePlayerState
-- ^ The player's name
, _cpsPosition :: String
-- ^ The player's position
, _cpsRookieFlag :: Maybe Bool
-- ^ Indicates whether or not the player is a rookie
, _cpsActiveFlag :: Maybe Bool
-- ^ Indicates whether or not the plauer is active
, _cpsSuccessCallback :: Action ()
-- ^ The function to call on success
, _cpsFailureCallback :: Action ()
@@ -321,10 +358,14 @@ data CreatePlayerState = CreatePlayerState
-- | Goalie creation status
data CreateGoalieState = CreateGoalieState
{ _cgsNumber :: Maybe Int
{ _cgsNumber :: Maybe Int
-- ^ The goalie's number
, _cgsName :: String
, _cgsName :: String
-- ^ The goalie's name
, _cgsRookieFlag :: Maybe Bool
-- ^ Indicates whether or not the goalie is a rookie
, _cgsActiveFlag :: Maybe Bool
-- ^ Indicates whether or not the goalie is active
, _cgsSuccessCallback :: Action ()
-- ^ The function to call on success
, _cgsFailureCallback :: Action ()
@@ -337,6 +378,8 @@ data EditPlayerState = EditPlayerState
-- ^ The index number of the player being edited
, _epsMode :: EditPlayerMode
-- ^ The editing mode
, _epsCallback :: Action ()
-- ^ The action to perform when the edit is complete
}
-- | Player editing mode
@@ -347,11 +390,12 @@ data EditPlayerMode
| EPPosition
| EPYtd
| EPLifetime
| EPYtdGoals
| EPYtdAssists
| EPDelete
| EPYtdGoals Bool
| EPYtdAssists Bool
| EPYtdPMin
| EPLtGoals
| EPLtAssists
| EPLtGoals Bool
| EPLtAssists Bool
| EPLtPMin
deriving (Eq, Show)
@@ -360,6 +404,9 @@ data EditGoalieState = EditGoalieState
{ _egsSelectedGoalie :: Maybe Int
-- ^ The index number of the 'Goalie' being edited
, _egsMode :: EditGoalieMode
-- ^ The editing mode
, _egsCallback :: Action ()
-- ^ The action to perform when the edit is complete
}
-- | 'Goalie' editing mode
@@ -369,20 +416,40 @@ data EditGoalieMode
| EGName
| EGYtd
| EGLifetime
| EGYtdGames
| EGYtdMins
| EGYtdGoals
| EGYtdWins
| EGYtdLosses
| EGDelete
| EGYtdGames Bool
| EGYtdMins Bool
| EGYtdGoals Bool
| EGYtdShutouts Bool
| EGYtdWins Bool
| EGYtdLosses Bool
| EGYtdTies
| EGLtGames
| EGLtMins
| EGLtGoals
| EGLtWins
| EGLtLosses
| EGLtGames Bool
| EGLtMins Bool
| EGLtGoals Bool
| EGLtShutouts Bool
| EGLtWins Bool
| EGLtLosses Bool
| EGLtTies
deriving (Eq, Show)
-- | Represents the standings edit mode
data EditStandingsMode
= ESMMenu
| ESMHome ESMSubMode
| ESMAway ESMSubMode
deriving (Eq, Show)
-- | Represents the standings edit sub-mode
data ESMSubMode
= ESMSubMenu
| ESMEditWins
| ESMEditLosses
| ESMEditOvertime
| ESMEditGoalsFor
| ESMEditGoalsAgainst
deriving (Eq, Show)
-- | Represents the database
data Database = Database
{ _dbPlayers :: [Player]
@@ -397,29 +464,6 @@ data Database = Database
-- ^ Statistics for away games
} deriving (Eq, Show)
instance FromJSON Database where
parseJSON = withObject "Database" $ \v -> Database
<$> v .: "players"
<*> v .: "goalies"
<*> v .: "games"
<*> v .: "home_game_stats"
<*> v .: "away_game_stats"
instance ToJSON Database where
toJSON (Database players goalies games hgs ags) = object
[ "players" .= players
, "goalies" .= goalies
, "games" .= games
, "home_game_stats" .= hgs
, "away_game_stats" .= ags
]
toEncoding (Database players goalies games hgs ags) = pairs $
"players" .= players <>
"goalies" .= goalies <>
"games" .= games <>
"home_game_stats" .= hgs <>
"away_game_stats" .= ags
-- | Represents a (non-goalie) player
data Player = Player
{ _pNumber :: Int
@@ -428,35 +472,16 @@ data Player = Player
-- ^ The player's name
, _pPosition :: String
-- ^ The player's position
, _pRookie :: Bool
-- ^ Indicates that the player is a rookie
, _pActive :: Bool
-- ^ Indicates that the player is active
, _pYtd :: PlayerStats
-- ^ The Player's year-to-date stats
, _pLifetime :: PlayerStats
-- ^ The player's lifetime stats
} deriving (Eq, Show)
instance FromJSON Player where
parseJSON = withObject "Player" $ \v -> Player
<$> v .: "number"
<*> v .: "name"
<*> v .: "position"
<*> v .: "ytd"
<*> v .: "lifetime"
instance ToJSON Player where
toJSON (Player num name pos ytd lt) = object
[ "number" .= num
, "name" .= name
, "position" .= pos
, "ytd" .= ytd
, "lifetime" .= lt
]
toEncoding (Player num name pos ytd lt) = pairs $
"number" .= num <>
"name" .= name <>
"position" .= pos <>
"ytd" .= ytd <>
"lifetime" .= lt
-- | Represents a (non-goalie) player's stats
data PlayerStats = PlayerStats
{ _psGoals :: Int
@@ -467,55 +492,22 @@ data PlayerStats = PlayerStats
-- ^ The number of penalty minutes
} deriving (Eq, Show)
instance FromJSON PlayerStats where
parseJSON = withObject "PlayerStats" $ \v -> PlayerStats
<$> v .: "goals"
<*> v .: "assists"
<*> v .: "penalty_mins"
instance ToJSON PlayerStats where
toJSON (PlayerStats g a pm) = object
[ "goals" .= g
, "assists" .= a
, "penalty_mins" .= pm
]
toEncoding (PlayerStats g a pm) = pairs $
"goals" .= g <>
"assists" .= a <>
"penalty_mins" .= pm
-- | Represents a goalie
data Goalie = Goalie
{ _gNumber :: Int
-- ^ The goalie's number
, _gName :: String
-- ^ The goalie's name
, _gRookie :: Bool
-- ^ Indicates that the goalie is a rookie
, _gActive :: Bool
-- ^ Indicates that the goalie is active
, _gYtd :: GoalieStats
-- ^ The goalie's year-to-date stats
, _gLifetime :: GoalieStats
-- ^ The goalie's lifetime stats
} deriving (Eq, Show)
instance FromJSON Goalie where
parseJSON = withObject "Goalie" $ \v -> Goalie
<$> v .: "number"
<*> v .: "name"
<*> v .: "ytd"
<*> v .: "lifetime"
instance ToJSON Goalie where
toJSON (Goalie num name ytd lt) = object
[ "number" .= num
, "name" .= name
, "ytd" .= ytd
, "lifetime" .= lt
]
toEncoding (Goalie num name ytd lt) = pairs $
"number" .= num <>
"name" .= name <>
"ytd" .= ytd <>
"lifetime" .= lt
-- | Represents a goalie's stats
data GoalieStats = GoalieStats
{ _gsGames :: Int
@@ -534,6 +526,168 @@ data GoalieStats = GoalieStats
-- ^ The number of ties
} deriving (Eq, Show)
-- | Game statistics
data GameStats = GameStats
{ _gmsWins :: Int
-- ^ Games won
, _gmsLosses :: Int
-- ^ Games lost
, _gmsOvertime :: Int
-- ^ Games lost in overtime
, _gmsGoalsFor :: Int
-- ^ Goals for the team
, _gmsGoalsAgainst :: Int
-- ^ Goals against the team
} deriving (Eq, Show)
-- | Defines a user prompt
data Prompt = Prompt
{ drawPrompt :: ProgState -> Widget ()
-- ^ Draws the prompt to the screen
, promptProcessChar :: Char -> Editor String () -> Editor String ()
-- ^ Modifies an editor based on the character entered
, promptAction :: String -> Action ()
-- ^ Action to perform when the value is entered
, promptSpecialKey :: Key -> [Modifier] -> Action ()
-- ^ Action to perform when a special key is pressed
}
-- | Parameters for a search prompt
data SelectParams a = SelectParams
{ spPrompt :: String
-- ^ The search prompt
, spSearchHeader :: String
-- ^ The header to display at the top of the search list
, spSearch :: String -> Database -> [(Int, a)]
-- ^ The search function
, spSearchExact :: String -> Database -> Maybe Int
-- ^ Search function looking for an exact match
, spElemDesc :: a -> String
-- ^ Provides a string description of an element
, spProcessChar :: Char -> Editor String () -> Editor String ()
-- ^ Processes a character entered by the user
, spCallback :: Maybe Int -> Action ()
-- ^ The function when the selection is made
, spNotFound :: String -> Action ()
-- ^ The function to call when the selection doesn't exist
}
-- | Describes a table cell
data TableCell
= CellText String
-- ^ A cell with text
| CellFill Char
-- ^ A cell filled with the given character
deriving (Eq, Show)
makeLenses ''ProgState
makeLenses ''GameState
makeLenses ''CreatePlayerState
makeLenses ''CreateGoalieState
makeLenses ''EditPlayerState
makeLenses ''EditGoalieState
makeLenses ''Database
makeLenses ''Player
makeLenses ''PlayerStats
makeLenses ''Goalie
makeLenses ''GoalieStats
makeLenses ''GameStats
instance FromJSON Database where
parseJSON = withObject "Database" $ \v -> Database
<$> v .:? "players" .!= []
<*> v .:? "goalies" .!= []
<*> v .:? "games" .!= 0
<*> v .:? "home_game_stats" .!= newGameStats
<*> v .:? "away_game_stats" .!= newGameStats
instance ToJSON Database where
toJSON (Database players goalies games hgs ags) = object
[ "players" .= players
, "goalies" .= goalies
, "games" .= games
, "home_game_stats" .= hgs
, "away_game_stats" .= ags
]
toEncoding (Database players goalies games hgs ags) = pairs $
"players" .= players <>
"goalies" .= goalies <>
"games" .= games <>
"home_game_stats" .= hgs <>
"away_game_stats" .= ags
instance FromJSON Player where
parseJSON = withObject "Player" $ \v -> Player
<$> v .: "number"
<*> v .: "name"
<*> v .: "position"
<*> v .:? "rookie" .!= False
<*> v .:? "active" .!= True
<*> v .:? "ytd" .!= newPlayerStats
<*> v .:? "lifetime" .!= newPlayerStats
instance ToJSON Player where
toJSON (Player num name pos rk act ytd lt) = object
[ "number" .= num
, "name" .= name
, "position" .= pos
, "rookie" .= rk
, "active" .= act
, "ytd" .= ytd
, "lifetime" .= lt
]
toEncoding (Player num name pos rk act ytd lt) = pairs $
"number" .= num <>
"name" .= name <>
"position" .= pos <>
"rookie" .= rk <>
"active" .= act <>
"ytd" .= ytd <>
"lifetime" .= lt
instance FromJSON PlayerStats where
parseJSON = withObject "PlayerStats" $ \v -> PlayerStats
<$> v .:? "goals" .!= 0
<*> v .:? "assists" .!= 0
<*> v .:? "penalty_mins" .!= 0
instance ToJSON PlayerStats where
toJSON (PlayerStats g a pm) = object
[ "goals" .= g
, "assists" .= a
, "penalty_mins" .= pm
]
toEncoding (PlayerStats g a pm) = pairs $
"goals" .= g <>
"assists" .= a <>
"penalty_mins" .= pm
instance FromJSON Goalie where
parseJSON = withObject "Goalie" $ \v -> Goalie
<$> v .: "number"
<*> v .: "name"
<*> v .:? "rookie" .!= False
<*> v .:? "active" .!= True
<*> v .:? "ytd" .!= newGoalieStats
<*> v .:? "lifetime" .!= newGoalieStats
instance ToJSON Goalie where
toJSON (Goalie num name rk act ytd lt) = object
[ "number" .= num
, "name" .= name
, "ytd" .= ytd
, "rookie" .= rk
, "active" .= act
, "lifetime" .= lt
]
toEncoding (Goalie num name rk act ytd lt) = pairs $
"number" .= num <>
"name" .= name <>
"rookie" .= rk <>
"active" .= act <>
"ytd" .= ytd <>
"lifetime" .= lt
instance FromJSON GoalieStats where
parseJSON = withObject "GoalieStats" $ \v -> GoalieStats
<$> v .:? "games" .!= 0
@@ -563,20 +717,6 @@ instance ToJSON GoalieStats where
"losses" .= l <>
"ties" .= t
-- | Game statistics
data GameStats = GameStats
{ _gmsWins :: Int
-- ^ Games won
, _gmsLosses :: Int
-- ^ 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
parseJSON = withObject "GameStats" $ \v -> GameStats
<$> v .: "wins"
@@ -600,59 +740,6 @@ instance ToJSON GameStats where
"goals_for" .= gf <>
"goals_against" .= ga
-- | Defines a user prompt
data Prompt = Prompt
{ promptDrawer :: ProgState -> C.Update ()
-- ^ Draws the prompt to the screen
, promptProcessChar :: Char -> String -> String
-- ^ Modifies the string based on the character entered
, promptAction :: String -> Action ()
-- ^ Action to perform when the value is entered
, promptSpecialKey :: C.Key -> Action ()
-- ^ Action to perform when a special key is pressed
}
-- | Parameters for a search prompt
data SelectParams a = SelectParams
{ spPrompt :: String
-- ^ The search prompt
, spSearchHeader :: String
-- ^ The header to display at the top of the search list
, spSearch :: String -> Database -> [(Int, a)]
-- ^ The search function
, spSearchExact :: String -> Database -> Maybe Int
-- ^ Search function looking for an exact match
, spElemDesc :: a -> String
-- ^ Provides a string description of an element
, spProcessChar :: Char -> String -> String
-- ^ Processes a character entered by the user
, spCallback :: Maybe Int -> Action ()
-- ^ The function when the selection is made
, spNotFound :: String -> Action ()
-- ^ The function to call when the selection doesn't exist
}
-- | Describes a table cell
data TableCell
= CellText String
-- ^ A cell with text
| CellFill Char
-- ^ A cell filled with the given character
deriving (Eq, Show)
makeLenses ''ProgState
makeLenses ''GameState
makeLenses ''CreatePlayerState
makeLenses ''CreateGoalieState
makeLenses ''EditPlayerState
makeLenses ''EditGoalieState
makeLenses ''Database
makeLenses ''Player
makeLenses ''PlayerStats
makeLenses ''Goalie
makeLenses ''GoalieStats
makeLenses ''GameStats
gameStateL :: Lens' ProgMode GameState
gameStateL = lens
(\case
@@ -688,13 +775,32 @@ editGoalieStateL = lens
_ -> newEditGoalieState)
(\_ egs -> EditGoalie egs)
editStandingsModeL :: Lens' ProgMode EditStandingsMode
editStandingsModeL = lens
(\case
EditStandings esm -> esm
_ -> ESMMenu)
(\_ esm -> EditStandings esm)
esmSubModeL :: Lens' EditStandingsMode ESMSubMode
esmSubModeL = lens
(\case
ESMMenu -> ESMSubMenu
ESMHome m -> m
ESMAway m -> m)
(\mode subMode -> case mode of
ESMMenu -> ESMMenu
ESMHome _ -> ESMHome subMode
ESMAway _ -> ESMAway subMode)
-- | Constructor for a 'ProgState'
newProgState :: ProgState
newProgState = ProgState
{ _database = newDatabase
, _progMode = MainMenu
, _inputBuffer = ""
, _scrollOffset = 0
{ _database = newDatabase
, _progMode = TitleScreen
, _dbName = ""
, _editorW = editor () (Just 1) ""
, _scroller = viewportScroll ()
}
-- | Constructor for a 'GameState'
@@ -730,6 +836,8 @@ newCreatePlayerState = CreatePlayerState
{ _cpsNumber = Nothing
, _cpsName = ""
, _cpsPosition = ""
, _cpsRookieFlag = Nothing
, _cpsActiveFlag = Nothing
, _cpsSuccessCallback = return ()
, _cpsFailureCallback = return ()
}
@@ -739,6 +847,8 @@ newCreateGoalieState :: CreateGoalieState
newCreateGoalieState = CreateGoalieState
{ _cgsNumber = Nothing
, _cgsName = ""
, _cgsRookieFlag = Nothing
, _cgsActiveFlag = Nothing
, _cgsSuccessCallback = return ()
, _cgsFailureCallback = return ()
}
@@ -748,6 +858,7 @@ newEditPlayerState :: EditPlayerState
newEditPlayerState = EditPlayerState
{ _epsSelectedPlayer = Nothing
, _epsMode = EPMenu
, _epsCallback = return ()
}
-- | Constructor for an 'EditGoalieState' value
@@ -755,6 +866,7 @@ newEditGoalieState :: EditGoalieState
newEditGoalieState = EditGoalieState
{ _egsSelectedGoalie = Nothing
, _egsMode = EGMenu
, _egsCallback = return ()
}
-- | Constructor for a 'Database'
@@ -780,6 +892,8 @@ newPlayer num name pos = Player
{ _pNumber = num
, _pName = name
, _pPosition = pos
, _pRookie = True
, _pActive = True
, _pYtd = newPlayerStats
, _pLifetime = newPlayerStats
}
@@ -802,6 +916,8 @@ newGoalie
newGoalie num name = Goalie
{ _gNumber = num
, _gName = name
, _gRookie = True
, _gActive = True
, _gYtd = newGoalieStats
, _gLifetime = newGoalieStats
}
@@ -899,6 +1015,23 @@ addGameStats s1 s2 = GameStats
, _gmsGoalsAgainst = s1^.gmsGoalsAgainst + s2^.gmsGoalsAgainst
}
-- | Searches through a list of players with a specified criteria
playerSearchWith
:: (Player -> Bool)
-- ^ The search criteria
-> String
-- ^ The search string
-> [Player]
-- ^ The list of players to search
-> [(Int, Player)]
-- ^ The matching players with their index numbers
playerSearchWith criteria sStr =
filter match . zip [0..]
where
match (_, p)
= map toUpper sStr `isInfixOf` map toUpper (p^.pName)
&& criteria p
-- | Searches through a list of players
playerSearch
:: String
@@ -907,9 +1040,17 @@ playerSearch
-- ^ The list of players to search
-> [(Int, Player)]
-- ^ The matching players with their index numbers
playerSearch sStr =
filter match . zip [0..]
where match (_, p) = map toUpper sStr `isInfixOf` map toUpper (p^.pName)
playerSearch = playerSearchWith $ const True
-- | Searches through a list of players for an active player
activePlayerSearch
:: String
-- ^ The search string
-> [Player]
-- ^ The list of players to search
-> [(Int, Player)]
-- ^ The matching players with their index numbers
activePlayerSearch = playerSearchWith (^.pActive)
-- | Searches for a player by exact match on name
playerSearchExact
@@ -920,7 +1061,7 @@ playerSearchExact
-> Maybe (Int, Player)
-- ^ The player's index and value
playerSearchExact sStr =
listToMaybe . filter match . zip [0..]
find match . zip [0..]
where match (_, p) = p^.pName == sStr
-- | Modifies a player with a given name
@@ -964,6 +1105,23 @@ addPlayerStats s1 s2 = newPlayerStats
& psAssists .~ s1^.psAssists + s2^.psAssists
& psPMin .~ s1^.psPMin + s2^.psPMin
-- | Searches a list of goalies with a search criteria
goalieSearchWith
:: (Goalie -> Bool)
-- ^ The search criteria
-> String
-- ^ The search string
-> [Goalie]
-- ^ The list to search
-> [(Int, Goalie)]
-- ^ The search results with their corresponding index numbers
goalieSearchWith criteria sStr =
filter match . zip [0..]
where
match (_, g)
= map toUpper sStr `isInfixOf` map toUpper (g^.gName)
&& criteria g
-- | Searches a list of goalies
goalieSearch
:: String
@@ -972,9 +1130,17 @@ goalieSearch
-- ^ The list to search
-> [(Int, Goalie)]
-- ^ The search results with their corresponding index numbers
goalieSearch sStr =
filter match . zip [0..]
where match (_, g) = map toUpper sStr `isInfixOf` map toUpper (g^.gName)
goalieSearch = goalieSearchWith $ const True
-- | Searches a list of goalies for an active goalie
activeGoalieSearch
:: String
-- ^ The search string
-> [Goalie]
-- ^ The list to search
-> [(Int, Goalie)]
-- ^ The search results with their corresponding index numbers
activeGoalieSearch = goalieSearchWith (^.gActive)
-- | Searches a list of goalies for an exact match
goalieSearchExact

View File

@@ -1,7 +1,7 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -39,6 +39,7 @@ module Mtlstats.Types.Menu (
import Lens.Micro ((^.))
import Lens.Micro.TH (makeLenses)
import Mtlstats.Format
import Mtlstats.Types
-- | Defines a menu
@@ -65,8 +66,15 @@ makeLenses ''Menu
makeLenses ''MenuItem
instance Show (Menu a) where
show m = m ^. menuTitle ++ "\n" ++ items
where items = unlines $ map show $ m ^. menuItems
show m = unlines
$ [ m^.menuTitle
, ""
]
++ body
where
body = map (left width) items
width = maximum $ map length items
items = map show $ m^.menuItems
instance Show (MenuItem a) where
show i = [i ^. miKey] ++ ") " ++ i ^. miDescription
show i = [i ^. miKey] ++ ": " ++ i ^. miDescription

View File

@@ -1,7 +1,7 @@
{- |
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -22,13 +22,23 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
module Mtlstats.Util
( nth
, modifyNth
, dropNth
, updateMap
, slice
, capitalizeName
, linesToWidget
, linesToWidgetC
, userText
) where
import Brick.Types (Widget)
import Brick.Widgets.Center (hCenter)
import Brick.Widgets.Core (str, vBox)
import Brick.Widgets.Edit (Editor, editContentsL, getEditContents)
import Data.Char (isSpace, toUpper)
import qualified Data.Map as M
import Data.Text.Zipper (insertChar)
import Lens.Micro ((^.), (&), (%~), to)
-- | Attempt to select the element from a list at a given index
nth
@@ -52,8 +62,21 @@ modifyNth
-> [a]
-- ^ The list
-> [a]
modifyNth n f = map (\(i, x) -> if i == n then f x else x)
. zip [0..]
modifyNth n f = zipWith
(\i x -> if i == n then f x else x)
[0..]
-- | Attempt to drop the nth element from a list
dropNth
:: Int
-- ^ The index of the element to drop
-> [a]
-- ^ The list to be modified
-> [a]
-- ^ The modified list
dropNth n = foldr
(\(i, x) acc -> if i == n then acc else x : acc)
[] . zip [0..]
-- | Modify a value indexed by a given key in a map using a default
-- initial value if not present
@@ -87,13 +110,14 @@ slice offset len = take len . drop offset
capitalizeName
:: Char
-- ^ The character being input
-> String
-> Editor String ()
-- ^ The current string
-> String
-> Editor String ()
-- ^ The resulting string
capitalizeName ch str = str ++ [ch']
capitalizeName ch e = e & editContentsL %~ insertChar ch'
where
ch' = if lockFlag str
s = e^.to userText
ch' = if lockFlag s
then toUpper ch
else ch
lockFlag "" = True
@@ -104,3 +128,22 @@ capitalizeName ch str = str ++ [ch']
lockFlag' (c:cs)
| isSpace c = lockFlag' cs
| otherwise = False
-- | Converts a list of lines to a widget
linesToWidget :: [String] -> Widget ()
linesToWidget = vBox . map (str . keepBlank)
-- | Converts a list of lines to a widget with each line horizontally
-- centered
linesToWidgetC :: [String] -> Widget ()
linesToWidgetC = vBox . map (hCenter . str . keepBlank)
-- | Fetches the text from an editor widget
userText :: Editor String () -> String
userText w = case getEditContents w of
(x:_) -> x
[] -> ""
keepBlank :: String -> String
keepBlank "" = " "
keepBlank s = s

View File

@@ -17,7 +17,7 @@
#
# resolver: ./custom-snapshot.yaml
# resolver: https://example.com/snapshots/2018-01-01.yaml
resolver: lts-14.0
resolver: lts-20.22
# User packages to be built.
# Various formats can be used as shown in the example below.

View File

@@ -7,13 +7,13 @@ packages:
- completed:
hackage: ncurses-0.2.16@sha256:8ad9fe6562a80d28166d76adbac1eb4d40c6511fe4e9272ed6e1166dc2f1cdf1,3575
pantry-tree:
size: 674
sha256: 093bdc85ed518c81724f5b6b81c24ab4ebdd231551861f4feaa43361136f70b7
size: 674
original:
hackage: ncurses-0.2.16@sha256:8ad9fe6562a80d28166d76adbac1eb4d40c6511fe4e9272ed6e1166dc2f1cdf1,3575
snapshots:
- completed:
size: 523443
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/14/0.yaml
sha256: 283773e7120f5446d961eab35ea95c9af9c24187cc178537bd29273200a05171
original: lts-14.0
sha256: dcf4fc28f12d805480ddbe8eb8c370e11db12f0461d0110a4240af27ac88d725
size: 650255
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/22.yaml
original: lts-20.22

View File

@@ -1,537 +0,0 @@
{-
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 Actions.EditGoalieSpec (spec) where
import Data.Maybe (fromJust)
import Lens.Micro ((^.), (&), (.~))
import Test.Hspec (Spec, context, describe, it, shouldBe)
import Mtlstats.Actions.EditGoalie
import Mtlstats.Types
import Mtlstats.Util
spec :: Spec
spec = describe "EditGoalie" $ do
editGoalieNumberSpec
editGoalieNameSpec
editGoalieYtdGamesSpec
editGoalieYtdMinsSpec
editGoalieYtdGoalsSpec
editGoalieYtdWinsSpec
editGoalieYtdLossesSpec
editGoalieYtdTiesSpec
editGoalieLtGamesSpec
editGoalieLtMinsSpec
editGoalieLtGoalsSpec
editGoalieLtWinsSpec
editGoalieLtLossesSpec
editGoalieLtTiesSpec
editGoalieNumberSpec :: Spec
editGoalieNumberSpec = describe "editGoalieNumber" $ editTest
(editGoalieNumber 5)
EGNumber
(uncurry newGoalie)
[ ( "set Joe"
, Just 0
, (5, "Joe")
, (3, "Bob")
, EGMenu
)
, ( "set Bob"
, Just 1
, (2, "Joe")
, (5, "Bob")
, EGMenu
)
, ( "out of bounds"
, Just 2
, (2, "Joe")
, (3, "Bob")
, EGNumber
)
, ( "no goalie selected"
, Nothing
, (2, "Joe")
, (3, "Bob")
, EGNumber
)
]
editGoalieNameSpec :: Spec
editGoalieNameSpec = describe "editGoalieName" $ editTest
(editGoalieName "foo")
EGName
(uncurry newGoalie)
[ ( "set Joe"
, Just 0
, ( 2, "foo" )
, ( 3, "Bob" )
, EGMenu
)
, ( "set Bob"
, Just 1
, ( 2, "Joe" )
, ( 3, "foo" )
, EGMenu
)
, ( "out of bounds"
, Just 2
, ( 2, "Joe" )
, ( 3, "Bob" )
, EGName
)
, ( "no goalie selected"
, Nothing
, ( 2, "Joe" )
, ( 3, "Bob" )
, EGName
)
]
editGoalieYtdGamesSpec :: Spec
editGoalieYtdGamesSpec = describe "editGoalieYtdGames" $ editTest
(editGoalieYtdGames 1)
EGYtdGames
(\(num, name, games) -> newGoalie num name & gYtd.gsGames .~ games)
[ ( "set Joe"
, Just 0
, ( 2, "Joe", 1 )
, ( 3, "Bob", 0 )
, EGYtd
)
, ( "set Bob"
, Just 1
, ( 2, "Joe", 0 )
, ( 3, "Bob", 1 )
, EGYtd
)
, ( "out of bounds"
, Just 2
, ( 2, "Joe", 0 )
, ( 3, "Bob", 0 )
, EGYtdGames
)
, ( "no goalie selected"
, Nothing
, ( 2, "Joe", 0 )
, ( 3, "Bob", 0 )
, EGYtdGames
)
]
editGoalieYtdMinsSpec :: Spec
editGoalieYtdMinsSpec = describe "editGoalieYtdMins" $ editTest
(editGoalieYtdMins 1)
EGYtdMins
(\(num, name, mins) -> newGoalie num name & gYtd.gsMinsPlayed .~ mins)
[ ( "set Joe"
, Just 0
, ( 2, "Joe", 1 )
, ( 3, "Bob", 0 )
, EGYtd
)
, ( "set Bob"
, Just 1
, (2, "Joe", 0 )
, (3, "Bob", 1 )
, EGYtd
)
, ( "out of bounds"
, Just 2
, ( 2, "Joe", 0 )
, ( 3, "Bob", 0 )
, EGYtdMins
)
, ( "no goalie selected"
, Nothing
, ( 2, "Joe", 0 )
, ( 3, "Bob", 0 )
, EGYtdMins
)
]
editGoalieYtdGoalsSpec :: Spec
editGoalieYtdGoalsSpec = describe "editGoalieYtdGoals" $ editTest
(editGoalieYtdGoals 1)
EGYtdGoals
(\(num, name, goals) -> newGoalie num name & gYtd.gsGoalsAllowed .~ goals)
[ ( "set Joe"
, Just 0
, ( 2, "Joe", 1 )
, ( 3, "Bob", 0 )
, EGYtd
)
, ( "set Bob"
, Just 1
, ( 2, "Joe", 0 )
, ( 3, "Bob", 1 )
, EGYtd
)
, ( "out of bounds"
, Just 2
, ( 2, "Joe", 0 )
, ( 3, "Bob", 0 )
, EGYtdGoals
)
, ( "no goalie selected"
, Nothing
, ( 2, "Joe", 0 )
, ( 3, "Bob", 0 )
, EGYtdGoals
)
]
editGoalieYtdWinsSpec :: Spec
editGoalieYtdWinsSpec = describe "editGoalieYtdWins" $ editTest
(editGoalieYtdWins 1)
EGYtdWins
(\(num, name, wins) -> newGoalie num name & gYtd.gsWins .~ wins)
[ ( "set Joe"
, Just 0
, ( 2, "Joe", 1 )
, ( 3, "Bob", 0 )
, EGYtd
)
, ( "set Bob"
, Just 1
, ( 2, "Joe", 0 )
, ( 3, "Bob", 1 )
, EGYtd
)
, ( "out of bounds"
, Just 2
, ( 2, "Joe", 0 )
, ( 3, "Bob", 0 )
, EGYtdWins
)
, ( "no goalie selected"
, Nothing
, ( 2, "Joe", 0 )
, ( 3, "Bob", 0 )
, EGYtdWins
)
]
editGoalieYtdLossesSpec :: Spec
editGoalieYtdLossesSpec = describe "editGoalieYtdLosses" $ editTest
(editGoalieYtdLosses 1)
EGYtdLosses
(\(num, name, losses) -> newGoalie num name & gYtd.gsLosses .~ losses)
[ ( "set Joe"
, Just 0
, ( 2, "Joe", 1 )
, ( 3, "Bob", 0 )
, EGYtd
)
, ( "set Bob"
, Just 1
, ( 2, "Joe", 0 )
, ( 3, "Bob", 1 )
, EGYtd
)
, ( "out of bounds"
, Just 2
, ( 2, "Joe", 0 )
, ( 3, "Bob", 0 )
, EGYtdLosses
)
, ( "no goalie selected"
, Nothing
, ( 2, "Joe", 0 )
, ( 3, "Bob", 0 )
, EGYtdLosses
)
]
editGoalieYtdTiesSpec :: Spec
editGoalieYtdTiesSpec = describe "editGoalieYtdTies" $ editTest
(editGoalieYtdTies 1)
EGYtdTies
(\(num, name, ties) -> newGoalie num name & gYtd.gsTies .~ ties)
[ ( "set Joe"
, Just 0
, ( 2, "Joe", 1 )
, ( 3, "Bob", 0 )
, EGYtd
)
, ( "set Bob"
, Just 1
, ( 2, "Joe", 0 )
, ( 3, "Bob", 1 )
, EGYtd
)
, ( "out of bounds"
, Just 2
, ( 2, "Joe", 0 )
, ( 3, "Bob", 0 )
, EGYtdTies
)
, ( "no goalie selected"
, Nothing
, ( 2, "Joe", 0 )
, ( 3, "Bob", 0 )
, EGYtdTies
)
]
editGoalieLtGamesSpec :: Spec
editGoalieLtGamesSpec = describe "editGoalieLtGames" $ editTest
(editGoalieLtGames 1)
EGLtGames
(\(num, name, games) -> newGoalie num name & gLifetime.gsGames .~ games)
[ ( "set Joe"
, Just 0
, ( 2, "Joe", 1 )
, ( 3, "Bob", 0 )
, EGLifetime
)
, ( "set Bob"
, Just 1
, ( 2, "Joe", 0 )
, ( 3, "Bob", 1 )
, EGLifetime
)
, ( "out of bounds"
, Just 2
, ( 2, "Joe", 0 )
, ( 3, "Bob", 0 )
, EGLtGames
)
, ( "no goalie selected"
, Nothing
, ( 2, "Joe", 0 )
, ( 3, "Bob", 0 )
, EGLtGames
)
]
editGoalieLtMinsSpec :: Spec
editGoalieLtMinsSpec = describe "editGoalieLtMins" $ editTest
(editGoalieLtMins 1)
EGLtMins
(\(num, name, mins) -> newGoalie num name & gLifetime.gsMinsPlayed .~ mins)
[ ( "set Joe"
, Just 0
, ( 2, "Joe", 1 )
, ( 3, "Bob", 0 )
, EGLifetime
)
, ( "set Bob"
, Just 1
, ( 2, "Joe", 0 )
, ( 3, "Bob", 1 )
, EGLifetime
)
, ( "out of bounds"
, Just 2
, ( 2, "Joe", 0 )
, ( 3, "Bob", 0 )
, EGLtMins
)
, ( "no goalie selected"
, Nothing
, ( 2, "Joe", 0 )
, ( 3, "Bob", 0 )
, EGLtMins
)
]
editGoalieLtGoalsSpec :: Spec
editGoalieLtGoalsSpec = describe "editGoalieLtGoals" $ editTest
(editGoalieLtGoals 1)
EGLtGoals
(\(num, name, goals) -> newGoalie num name & gLifetime.gsGoalsAllowed .~ goals)
[ ( "set Joe"
, Just 0
, ( 2, "Joe", 1 )
, ( 3, "Bob", 0 )
, EGLifetime
)
, ( "set Bob"
, Just 1
, ( 2, "Joe", 0 )
, ( 3, "Bob", 1 )
, EGLifetime
)
, ( "out of bounds"
, Just 2
, ( 2, "Joe", 0 )
, ( 3, "Bob", 0 )
, EGLtGoals
)
, ( "no goalie selected"
, Nothing
, ( 2, "Joe", 0 )
, ( 3, "Bob", 0 )
, EGLtGoals
)
]
editGoalieLtWinsSpec :: Spec
editGoalieLtWinsSpec = describe "editGoalieLtWins" $ editTest
(editGoalieLtWins 1)
EGLtWins
(\(num, name, wins) -> newGoalie num name & gLifetime.gsWins .~ wins)
[ ( "set Joe"
, Just 0
, ( 2, "Joe", 1 )
, ( 3, "Bob", 0 )
, EGLifetime
)
, ( "set Bob"
, Just 1
, ( 2, "Joe", 0 )
, ( 3, "Bob", 1 )
, EGLifetime
)
, ( "out of bounds"
, Just 2
, ( 2, "Joe", 0 )
, ( 3, "Bob", 0 )
, EGLtWins
)
, ( "no goalie selected"
, Nothing
, ( 2, "Joe", 0 )
, ( 3, "Bob", 0 )
, EGLtWins
)
]
editGoalieLtLossesSpec :: Spec
editGoalieLtLossesSpec = describe "editGoalieLtLosses" $ editTest
(editGoalieLtLosses 1)
EGLtLosses
(\(num, name, losses) -> newGoalie num name & gLifetime.gsLosses .~ losses)
[ ( "set Joe"
, Just 0
, ( 2, "Joe", 1 )
, ( 3, "Bob", 0 )
, EGLifetime
)
, ( "set Bob"
, Just 1
, ( 2, "Joe", 0 )
, ( 3, "Bob", 1 )
, EGLifetime
)
, ( "out of bounds"
, Just 2
, ( 2, "Joe", 0 )
, ( 3, "Bob", 0 )
, EGLtLosses
)
, ( "no goalie selected"
, Nothing
, ( 2, "Joe", 0 )
, ( 3, "Bob", 0 )
, EGLtLosses
)
]
editGoalieLtTiesSpec :: Spec
editGoalieLtTiesSpec = describe "editGoalieLtTies" $ editTest
(editGoalieLtTies 1)
EGLtTies
(\(num, name, ties) -> newGoalie num name & gLifetime.gsTies .~ ties)
[ ( "set Joe"
, Just 0
, ( 2, "Joe", 1 )
, ( 3, "Bob", 0 )
, EGLifetime
)
, ( "set Bob"
, Just 1
, ( 2, "Joe", 0 )
, ( 3, "Bob", 1 )
, EGLifetime
)
, ( "out of bounds"
, Just 2
, ( 2, "Joe", 0 )
, ( 3, "Bob", 0 )
, EGLtTies
)
, ( "no goalie selected"
, Nothing
, ( 2, "Joe", 0 )
, ( 3, "Bob", 0 )
, EGLtTies
)
]
editTest
:: (ProgState -> ProgState)
-> EditGoalieMode
-> (a -> Goalie)
-> [(String, Maybe Int, a, a, EditGoalieMode)]
-> Spec
editTest func setMode mkGoalie params = do
mapM_
(\(setLabel, setGid, joeData, bobData, expectMode) -> context setLabel $ do
let
egs = newEditGoalieState
& egsSelectedGoalie .~ setGid
& egsMode .~ setMode
ps = func $ progState $ EditGoalie egs
mapM_
(\(chkLabel, chkGid, goalieData) -> context chkLabel $ let
actual = fromJust $ nth chkGid $ ps^.database.dbGoalies
expected = mkGoalie goalieData
in it ("should be " ++ show expected) $
actual `shouldBe` expected)
-- label, goalie ID, goalie data
[ ( "check Joe", 0, joeData )
, ( "check Bob", 1, bobData )
]
context "check mode" $
it ("should be " ++ show expectMode) $
ps^.progMode.editGoalieStateL.egsMode `shouldBe` expectMode)
params
context "wrong progMode" $ do
let ps = func $ progState MainMenu
it "should not change the database" $
ps^.database `shouldBe` db
it "should not change the progMode" $
show (ps^.progMode) `shouldBe` "MainMenu"
joe :: Goalie
joe = newGoalie 2 "Joe"
bob :: Goalie
bob = newGoalie 3 "Bob"
db :: Database
db = newDatabase & dbGoalies .~ [joe, bob]
progState :: ProgMode -> ProgState
progState mode = newProgState
& progMode .~ mode
& database .~ db

View File

@@ -0,0 +1,83 @@
{-
mtlstats
Copyright (C) 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/>.
-}
{-# LANGUAGE LambdaCase #-}
module Actions.EditStandingsSpec (spec) where
import Lens.Micro ((^.), (&), (.~))
import Test.Hspec
( Spec
, context
, describe
, it
, shouldBe
, shouldSatisfy
)
import Mtlstats.Actions.EditStandings
import Mtlstats.Types
spec :: Spec
spec = describe "EditStandings" $ do
mapM_
(\(label, f, expected) -> describe label $ do
let
ps = newProgState
ps' = f ps
it "should set progMode to EditStandings" $
ps'^.progMode `shouldSatisfy` \case
(EditStandings _) -> True
_ -> False
it ("should set editStandingsMode to " ++ show expected) $
ps'^.progMode.editStandingsModeL `shouldBe` expected)
-- label, function, expected mode
[ ( "editStandings", editStandings, ESMMenu )
, ( "editHomeStandings", editHomeStandings, ESMHome ESMSubMenu )
, ( "editAwayStandings", editAwayStandings, ESMAway ESMSubMenu )
]
mapM_
(\(label, f, expected) -> describe label $ do
mapM_
(\prefix -> context ("mode: " ++ show (prefix ESMSubMenu)) $ let
ps = newProgState & progMode.editStandingsModeL .~ prefix ESMSubMenu
ps' = f ps
in it ("should set the mode to " ++ show expected) $
ps'^.progMode.editStandingsModeL `shouldBe` prefix expected)
[ESMHome, ESMAway]
context "mode: ESMMenu" $ let
ps = newProgState & progMode.editStandingsModeL .~ ESMMenu
ps' = f ps
in it "should not change the mode" $
ps'^.progMode.editStandingsModeL `shouldBe` ESMMenu)
-- label, function, expected
[ ( "editWins", editWins, ESMEditWins )
, ( "editLosses", editLosses, ESMEditLosses )
, ( "editOvertime", editOvertime, ESMEditOvertime )
, ( "editGoalsFor", editGoalsFor, ESMEditGoalsFor )
, ( "editGoalsAgainst", editGoalsAgainst, ESMEditGoalsAgainst )
]

View File

@@ -1,7 +1,7 @@
{-
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -23,7 +23,7 @@ module Actions.NewGame.GoalieInputSpec (spec) where
import qualified Data.Map as M
import Data.Maybe (fromJust)
import Lens.Micro ((^.), (&), (.~), (?~))
import Lens.Micro ((^.), (&), (.~), (?~), (%~))
import Test.Hspec (Spec, context, describe, it, shouldBe)
import Mtlstats.Actions.NewGame.GoalieInput
@@ -33,27 +33,50 @@ import Mtlstats.Util
import qualified TypesSpec as TS
spec :: Spec
spec = describe "Mtlstats.Actions.GoalieInput" $ do
spec = describe "GoalieInput" $ do
finishGoalieEntrySpec
recordGoalieStatsSpec
setGameGoalieSpec
finishGoalieEntrySpec :: Spec
finishGoalieEntrySpec = describe "finishGoalieEntry" $ do
let
progState stats = newProgState
& progMode.gameStateL.gameGoalieStats .~ stats
& finishGoalieEntry
finishGoalieEntrySpec = describe "finishGoalieEntry" $ mapM_
(\(label, stats, grFlag, gaFlag) -> context label $ do
let
ps = newProgState
& progMode.gameStateL
%~ (gameGoalieStats .~ stats)
. (gameType ?~ HomeGame)
. (homeScore ?~ 1)
. (awayScore ?~ 0)
. (overtimeFlag ?~ False)
& database.dbGoalies .~ goalies
context "no goalie data" $
it "should not set goaliesRecorded" $ let
s = progState M.empty
in s^.progMode.gameStateL.gameGoaliesRecorded `shouldBe` False
ps' = finishGoalieEntry ps
gs = ps'^.progMode.gameStateL
context "goalie data" $
it "should set goaliesRecorded" $ let
s = progState $ M.fromList [(1, newGoalieStats)]
in s^.progMode.gameStateL.gameGoaliesRecorded `shouldBe` True
describe "gameGoaliesRecorded" $
it ("should be " ++ show grFlag) $
gs^.gameGoaliesRecorded `shouldBe` grFlag
describe "gameGoalieAssigned" $
it ("should be " ++ show gaFlag) $
gs^.gameGoalieAssigned `shouldBe` gaFlag)
-- label, initial stats, goalies recorded, goalie assigned
[ ( "no goalies", noGoalies, False, False )
, ( "one goalie", oneGoalie, True, True )
, ( "two goalies", twoGoalies, True, False )
]
where
goalies = [joe, bob]
joe = newGoalie 2 "Joe"
bob = newGoalie 3 "Bob"
noGoalies = M.empty
oneGoalie = M.fromList [joeStats]
twoGoalies = M.fromList [joeStats, bobStats]
joeStats = (0, newGoalieStats)
bobStats = (1, newGoalieStats)
recordGoalieStatsSpec :: Spec
recordGoalieStatsSpec = describe "recordGoalieStats" $ let
@@ -185,107 +208,179 @@ recordGoalieStatsSpec = describe "recordGoalieStats" $ let
]
setGameGoalieSpec :: Spec
setGameGoalieSpec = describe "setGameGoalie" $ let
setGameGoalieSpec = describe "setGameGoalie" $ mapM_
goalieStats w l t = newGoalieStats
& gsWins .~ w
& gsLosses .~ l
& gsTies .~ t
(\(label, goalieId, ps, expectedJoe, expectedBob, expectedGStats) ->
context label $ do
bob = newGoalie 2 "Bob"
& gYtd .~ goalieStats 10 11 12
& gLifetime .~ goalieStats 20 21 22
let
ps' = setGameGoalie goalieId ps
(joe', bob') = getFirstTwo $ ps'^.database.dbGoalies
gStats' = ps'^.progMode.gameStateL.gameGoalieStats
joe = newGoalie 3 "Joe"
& gYtd .~ goalieStats 30 31 32
& gLifetime .~ goalieStats 40 41 42
context "Joe" $ joe' `TS.compareTest` expectedJoe
context "Bob" $ bob' `TS.compareTest` expectedBob
context "game stats" $ gStats' `TS.compareTest` expectedGStats)
gameState h a ot = newGameState
& gameType ?~ HomeGame
& homeScore ?~ h
& awayScore ?~ a
& overtimeFlag ?~ ot
[ ( "Joe wins - no shutout"
, 0
, psWin
, joeWin
, bob
, gsJoeWin
)
winningGame = gameState 1 0 False
losingGame = gameState 0 1 False
tiedGame = gameState 0 1 True
, ( "Bob wins - no shutout"
, 1
, psWin
, joe
, bobWin
, gsBobWin
)
in mapM_
(\(setLabel, gs, setGid, bobData, joeData) -> context setLabel $ let
, ( "Joe wins - shutout"
, 0
, psWinSO
, joeWinSO
, bob
, gsJoeWinSO
)
progState = newProgState
& database.dbGoalies .~ [bob, joe]
& progMode.gameStateL .~ gs
& setGameGoalie setGid
, ( "Bob wins - shutout"
, 1
, psWinSO
, joe
, bobWinSO
, gsBobWinSO
)
in mapM_
(\( chkLabel
, chkGid
, ( gWins
, gLosses
, gTies
, ytdWins
, ytdLosses
, ytdTies
, ltWins
, ltLosses
, ltTies
)
) -> context chkLabel $ do
let
goalie = (progState^.database.dbGoalies) !! chkGid
gameStats = progState^.progMode.gameStateL.gameGoalieStats
game = M.findWithDefault newGoalieStats chkGid gameStats
ytd = goalie^.gYtd
lifetime = goalie^.gLifetime
, ( "Joe loses"
, 0
, psLose
, joeLose
, bob
, gsJoeLose
)
mapM_
(\(label', expected, actual) -> context label' $
expected `TS.compareTest` actual)
[ ( "game stats", game, goalieStats gWins gLosses gTies )
, ( "YTD stats", ytd, goalieStats ytdWins ytdLosses ytdTies )
, ( "lifetime stats", lifetime, goalieStats ltWins ltLosses ltTies )
]
, ( "Bob loses"
, 1
, psLose
, joe
, bobLose
, gsBobLose
)
it "should set the gameGoalieAssigned flag" $
progState^.progMode.gameStateL.gameGoalieAssigned `shouldBe` True)
[ ( "checking Bob", 0, bobData )
, ( "checking Joe", 1, joeData )
])
[ ( "Bob wins"
, winningGame
, 0
, ( 1, 0, 0, 11, 11, 12, 21, 21, 22 )
, ( 0, 0, 0, 30, 31, 32, 40, 41, 42 )
)
, ( "Bob loses"
, losingGame
, 0
, ( 0, 1, 0, 10, 12, 12, 20, 22, 22 )
, ( 0, 0, 0, 30, 31, 32, 40, 41, 42 )
)
, ( "Bob ties"
, tiedGame
, 0
, ( 0, 0, 1, 10, 11, 13, 20, 21, 23 )
, ( 0, 0, 0, 30, 31, 32, 40, 41, 42 )
)
, ( "Joe wins"
, winningGame
, 1
, ( 0, 0, 0, 10, 11, 12, 20, 21, 22 )
, ( 1, 0, 0, 31, 31, 32, 41, 41, 42 )
)
, ( "Joe loses"
, losingGame
, 1
, ( 0, 0, 0, 10, 11, 12, 20, 21, 22 )
, ( 0, 1, 0, 30, 32, 32, 40, 42, 42 )
)
, ( "Joe ties"
, tiedGame
, 1
, ( 0, 0, 0, 10, 11, 12, 20, 21, 22 )
, ( 0, 0, 1, 30, 31, 33, 40, 41, 43 )
)
]
, ( "Joe overtime"
, 0
, psOT
, joeOT
, bob
, gsJoeOT
)
, ( "Bob overtime"
, 1
, psOT
, joe
, bobOT
, gsBobOT
)
]
where
joe
= newGoalie 2 "Joe"
& gYtd
%~ (gsShutouts .~ 11)
. (gsWins .~ 12)
. (gsLosses .~ 13)
. (gsTies .~ 14)
& gLifetime
%~ (gsShutouts .~ 21)
. (gsWins .~ 22)
. (gsLosses .~ 23)
. (gsTies .~ 24)
bob
= newGoalie 3 "Bob"
& gYtd
%~ (gsShutouts .~ 31)
. (gsWins .~ 32)
. (gsLosses .~ 33)
. (gsTies .~ 34)
& gLifetime
%~ (gsShutouts .~ 41)
. (gsWins .~ 42)
. (gsLosses .~ 43)
. (gsTies .~ 44)
joeWin = win joe
bobWin = win bob
joeWinSO = winSO joe
bobWinSO = winSO bob
joeLose = lose joe
bobLose = lose bob
joeOT = tie joe
bobOT = tie bob
psWin = mkProgState
$ (homeScore ?~ 2)
. (awayScore ?~ 1)
psWinSO = mkProgState
$ (homeScore ?~ 1)
. (awayScore ?~ 0)
psLose = mkProgState
$ (homeScore ?~ 0)
. (awayScore ?~ 1)
psOT = mkProgState
$ (homeScore ?~ 0)
. (awayScore ?~ 1)
. (overtimeFlag ?~ True)
mkProgState f
= newProgState
& database.dbGoalies .~ [joe, bob]
& progMode.gameStateL
%~ f
. (gameType ?~ HomeGame)
. (overtimeFlag ?~ False)
gsJoeWin = mkGameStats 0 incWin
gsBobWin = mkGameStats 1 incWin
gsJoeWinSO = mkGameStats 0 $ incWin . incSO
gsBobWinSO = mkGameStats 1 $ incWin . incSO
gsJoeLose = mkGameStats 0 incLoss
gsBobLose = mkGameStats 1 incLoss
gsJoeOT = mkGameStats 0 incOT
gsBobOT = mkGameStats 1 incOT
mkGameStats n f = M.fromList [(n, f newGoalieStats)]
win
= (gYtd %~ incWin)
. (gLifetime %~ incWin)
winSO
= (gYtd %~ (incWin . incSO))
. (gLifetime %~ (incWin . incSO))
lose
= (gYtd %~ incLoss)
. (gLifetime %~ incLoss)
tie
= (gYtd %~ incOT)
. (gLifetime %~ incOT)
incWin = gsWins %~ succ
incSO = gsShutouts %~ succ
incLoss = gsLosses %~ succ
incOT = gsTies %~ succ
getFirstTwo :: [a] -> (a, a)
getFirstTwo (x:y:_) = (x, y)
getFirstTwo _ = error "insufficient members of list"

View File

@@ -1,7 +1,7 @@
{-
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -48,61 +48,31 @@ spec = describe "NewGame" $ do
GoalieInput.spec
overtimeCheckSpec :: Spec
overtimeCheckSpec = describe "overtimeCheck" $ do
overtimeCheckSpec = describe "overtimeCheck" $ mapM_
(\(label, expectation, gt, home, away, otf) ->
context label $
it expectation $ let
ps = newProgState & progMode.gameStateL
%~ (gameType ?~ gt)
. (homeScore ?~ home)
. (awayScore ?~ away)
context "tie game" $ do
let
s = newProgState
& progMode.gameStateL
%~ (gameType ?~ HomeGame)
. (homeScore ?~ 1)
. (awayScore ?~ 1)
& overtimeCheck
ps' = overtimeCheck ps
in ps'^.progMode.gameStateL.overtimeFlag `shouldBe` otf)
it "should clear the home score" $
s^.progMode.gameStateL.homeScore `shouldBe` Nothing
-- label, expectation, type, home, away, ot flag
[ ( "home win", clearFlag, HomeGame, 2, 1, Just False )
, ( "home loss", leaveFlag, HomeGame, 1, 2, Nothing )
, ( "home tie", setFlag, HomeGame, 1, 1, Just True )
, ( "away win", clearFlag, AwayGame, 1, 2, Just False )
, ( "away loss", leaveFlag, AwayGame, 2, 1, Nothing )
, ( "away tie", setFlag, AwayGame, 1, 1, Just True )
]
it "should clear the away score" $
s^.progMode.gameStateL.awayScore `shouldBe` Nothing
it "should leave the overtimeFlag blank" $
s^.progMode.gameStateL.overtimeFlag `shouldBe` Nothing
context "game won" $ do
let
s = newProgState
& progMode.gameStateL
%~ (gameType ?~ HomeGame)
. (homeScore ?~ 2)
. (awayScore ?~ 1)
& overtimeCheck
it "should not change the home score" $
s^.progMode.gameStateL.homeScore `shouldBe` Just 2
it "should not change the away score" $
s^.progMode.gameStateL.awayScore `shouldBe` Just 1
it "should set the overtimeCheck flag to False" $
s^.progMode.gameStateL.overtimeFlag `shouldBe` Just False
context "game lost" $ do
let
s = newProgState
& progMode.gameStateL
%~ (gameType ?~ HomeGame)
. (homeScore ?~ 1)
. (awayScore ?~ 2)
& overtimeCheck
it "should not change the home score" $
s^.progMode.gameStateL.homeScore `shouldBe` Just 1
it "should not change the away score" $
s^.progMode.gameStateL.awayScore `shouldBe` Just 2
it "should leave the overtimeCheck flag blank" $
s^.progMode.gameStateL.overtimeFlag `shouldBe` Nothing
where
clearFlag = "should set the overtimeFlag to True"
setFlag = "should set the overtimeFlag to False"
leaveFlag = "should leave the overtimeFlag as Nothing"
updateGameStatsSpec :: Spec
updateGameStatsSpec = describe "updateGameStats" $ do

View File

@@ -1,7 +1,7 @@
{-
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -24,7 +24,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
module ActionsSpec (spec) where
import Control.Monad (replicateM)
import Lens.Micro ((^.), (&), (.~), (?~))
import Lens.Micro ((^.), (&), (.~), (?~), (%~), to)
import Test.Hspec
( Spec
, context
@@ -37,9 +37,11 @@ import Test.Hspec
import Mtlstats.Actions
import Mtlstats.Types
import Mtlstats.Util
import qualified Actions.EditGoalieSpec as EditGoalie
import qualified Actions.NewGameSpec as NewGame
import qualified Actions.EditStandingsSpec as EditStandings
import SpecHelpers
import qualified TypesSpec as TS
spec :: Spec
@@ -47,23 +49,22 @@ spec = describe "Mtlstats.Actions" $ do
startNewSeasonSpec
startNewGameSpec
resetYtdSpec
clearRookiesSpec
resetStandingsSpec
addCharSpec
removeCharSpec
createPlayerSpec
createGoalieSpec
editSpec
editPlayerSpec
editSelectedPlayerSpec
editGoalieSpec
editSelectedGoalieSpec
addPlayerSpec
addGoalieSpec
resetCreatePlayerStateSpec
resetCreateGoalieStateSpec
backHomeSpec
scrollUpSpec
scrollDownSpec
NewGame.spec
EditGoalie.spec
EditStandings.spec
startNewSeasonSpec :: Spec
startNewSeasonSpec = describe "startNewSeason" $ do
@@ -129,6 +130,45 @@ resetYtdSpec = describe "resetYtd" $
lt ^. gsTies `shouldNotBe` 0) $
s ^. database . dbGoalies
clearRookiesSpec :: Spec
clearRookiesSpec = describe "clearRookies" $ do
let
players =
[ newPlayer 1 "Joe" "centre" & pRookie .~ True
, newPlayer 2 "Bob" "centre" & pRookie .~ False
]
goalies =
[ newGoalie 3 "Bill" & gRookie .~ True
, newGoalie 4 "Doug" & gRookie .~ False
]
ps = newProgState
& database
%~ (dbPlayers .~ players)
. (dbGoalies .~ goalies)
ps' = clearRookies ps
context "Players" $ mapM_
(\p -> let
name = p^.pName
rFlag = p^.pRookie
in context name $
it "should not be a rookie" $
rFlag `shouldBe` False)
(ps'^.database.dbPlayers)
context "Goalies" $ mapM_
(\g -> let
name = g^.gName
rFlag = g^.gRookie
in context name $
it "should not be a rookie" $
rFlag `shouldBe` False)
(ps'^.database.dbGoalies)
resetStandingsSpec :: Spec
resetStandingsSpec = describe "resetStandings" $ do
let
@@ -164,29 +204,6 @@ resetStandingsSpec = describe "resetStandings" $ do
it "should be reset" $
ps^.database.dbAwayGameStats `shouldBe` newGameStats
addCharSpec :: Spec
addCharSpec = describe "addChar" $
it "should add the character to the input buffer" $ let
s = newProgState
& inputBuffer .~ "foo"
& addChar 'd'
in s ^. inputBuffer `shouldBe` "food"
removeCharSpec :: Spec
removeCharSpec = describe "removeChar" $ do
context "empty" $
it "should remove the character from the input buffer" $ let
s = removeChar newProgState
in s ^. inputBuffer `shouldBe` ""
context "not empty" $
it "should remove the character from the input buffer" $ let
s = newProgState
& inputBuffer .~ "foo"
& removeChar
in s ^. inputBuffer `shouldBe` "fo"
createPlayerSpec :: Spec
createPlayerSpec = describe "createPlayer" $
it "should change the mode appropriately" $ let
@@ -211,58 +228,152 @@ editPlayerSpec = describe "editPlayer" $
s = editPlayer newProgState
in show (s^.progMode) `shouldBe` "EditPlayer"
editSelectedPlayerSpec :: Spec
editSelectedPlayerSpec = describe "editSelectedPlayer" $ mapM_
(\(label, pState, expected) -> context label $
it "should edit the players appropriately" $ let
pState' = editSelectedPlayer (pName .~ "foo") pState
players' = pState'^.database.dbPlayers
in players' `shouldBe` expected)
-- label, initial state, expected
[ ( "wrong mode", baseState, players )
, ( "not selected", changePlayer Nothing, players )
, ( "player 0", changePlayer $ Just 0, changed0 )
, ( "player 1", changePlayer $ Just 1, changed1 )
, ( "out of bounds", changePlayer $ Just 2, players )
]
where
baseState = newProgState & database.dbPlayers .~ players
changePlayer n = baseState
& (progMode.editPlayerStateL.epsSelectedPlayer .~ n)
players = [ player 0, player 1 ]
changed0 = [ player' 0, player 1 ]
changed1 = [ player 0, player' 1 ]
player n = newPlayer n ("Player " ++ show n) "pos"
player' n = newPlayer n "foo" "pos"
editGoalieSpec :: Spec
editGoalieSpec = describe "editGoalie" $
it "should change the mode appropriately" $ let
s = editGoalie newProgState
in show (s^.progMode) `shouldBe` "EditGoalie"
editSelectedGoalieSpec :: Spec
editSelectedGoalieSpec = describe "editSelectedGoalie" $ mapM_
(\(label, pState, expected) -> context label $
it "should edit the goalies appropriately" $ let
pState' = editSelectedGoalie (gName .~ "foo") pState
goalies' = pState'^.database.dbGoalies
in goalies' `shouldBe` expected)
-- label, initial state, expected
[ ( "wrong mode", baseState, goalies )
, ( "not selected", changeGoalie Nothing, goalies )
, ( "goalie 0", changeGoalie $ Just 0, changed0 )
, ( "goalie 1", changeGoalie $ Just 1, changed1 )
, ( "out of bounds", changeGoalie $ Just 2, goalies )
]
where
baseState = newProgState & database.dbGoalies .~ goalies
changeGoalie n = baseState
& (progMode.editGoalieStateL.egsSelectedGoalie .~ n)
goalies = [ goalie 0, goalie 1 ]
changed0 = [ goalie' 0, goalie 1 ]
changed1 = [ goalie 0, goalie' 1 ]
goalie n = newGoalie n ("Player " ++ show n)
goalie' n = newGoalie n "foo"
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
addPlayerSpec = describe "addPlayer" $ mapM_
(\(label, expectation, pm, players) -> context label $
it expectation $ let
ps = newProgState
& progMode .~ pm
& database.dbPlayers .~ [joe]
ps' = addPlayer ps
in ps'^.database.dbPlayers `shouldBe` players)
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]
-- label, expectation, progMode, players
[ ( "wrong mode", failure, MainMenu, [joe] )
, ( "missing number", failure, noNum, [joe] )
, ( "missing rookie flag", failure, noRookie, [joe] )
, ( "missing active flag", failure, noActive, [joe] )
, ( "rookie", success, mkRookie, [joe, rookie] )
, ( "retired", success, mkRetired, [joe, retired] )
, ( "normal player", success, mkNormal, [joe, normal] )
]
context "data unavailable" $
it "should not create the player" $ let
s' = addPlayer $ s MainMenu
in s'^.database.dbPlayers `shouldBe` [p1]
where
failure = "should not create the player"
success = "should create the player"
noNum = mkpm Nothing (Just False) (Just True)
noRookie = mkpm (Just 3) Nothing (Just True)
noActive = mkpm (Just 3) (Just False) Nothing
mkRookie = mkpm (Just 3) (Just True) (Just True)
mkRetired = mkpm (Just 3) (Just False) (Just False)
mkNormal = mkpm (Just 3) (Just False) (Just True)
joe = newPlayer 2 "Joe" "centre"
rookie = player True True
retired = player False False
normal = player False True
player r a = newPlayer 3 "Bob" "defense"
& pRookie .~ r
& pActive .~ a
mkpm n r a = CreatePlayer $ newCreatePlayerState
& cpsNumber .~ n
& cpsName .~ "Bob"
& cpsPosition .~ "defense"
& cpsRookieFlag .~ r
& cpsActiveFlag .~ a
addGoalieSpec :: Spec
addGoalieSpec = describe "addGoalie" $ do
let
g1 = newGoalie 2 "Joe"
g2 = newGoalie 3 "Bob"
db = newDatabase
& dbGoalies .~ [g1]
s pm = newProgState
& database .~ db
& progMode .~ pm
addGoalieSpec = describe "addGoalie" $ mapM_
(\(label, expectation, pm, goalies) -> context label $
it expectation $ let
ps = newProgState
& progMode .~ pm
& database.dbGoalies .~ [joe]
ps' = addGoalie ps
in ps'^.database.dbGoalies `shouldBe` goalies)
context "data available" $
it "should create the goalie" $ let
s' = addGoalie $ s $ CreateGoalie $ newCreateGoalieState
& cgsNumber ?~ 3
& cgsName .~ "Bob"
in s'^.database.dbGoalies `shouldBe` [g1, g2]
-- label, expectation, progMode, expected goalies
[ ( "wrong mode", failure, MainMenu, [joe] )
, ( "no number", failure, noNum, [joe] )
, ( "no rookie flag", failure, noRookie, [joe] )
, ( "no active flag", failure, noActive, [joe] )
, ( "rookie", success, mkRookie, [joe, rookie] )
, ( "retired", success, mkRetired, [joe, retired] )
, ( "normal goalie", success, mkNormal, [joe, normal] )
]
context "data unavailable" $
it "should not create the goalie" $ let
s' = addGoalie $ s MainMenu
in s'^.database.dbGoalies `shouldBe` [g1]
where
failure = "should not create the goalie"
success = "should create the goalie"
noNum = cgs Nothing (Just False) (Just True)
noRookie = cgs (Just 3) Nothing (Just True)
noActive = cgs (Just 3) (Just False) Nothing
mkRookie = cgs (Just 3) (Just True) (Just True)
mkRetired = cgs (Just 3) (Just False) (Just False)
mkNormal = cgs (Just 3) (Just False) (Just True)
joe = newGoalie 2 "Joe"
rookie = goalie True True
retired = goalie False False
normal = goalie False True
goalie r a = newGoalie 3 "Bob"
& gRookie .~ r
& gActive .~ a
cgs n r a = CreateGoalie $ newCreateGoalieState
& cgsNumber .~ n
& cgsName .~ "Bob"
& cgsRookieFlag .~ r
& cgsActiveFlag .~ a
resetCreatePlayerStateSpec :: Spec
resetCreatePlayerStateSpec = describe "resetCreatePlayerState" $ let
@@ -288,8 +399,7 @@ backHomeSpec = describe "backHome" $ do
let
input = newProgState
& progMode.gameStateL .~ newGameState
& inputBuffer .~ "foo"
& scrollOffset .~ 123
& editorW .~ mkEditor "foo"
result = backHome input
it "should set the program mode back to MainMenu" $
@@ -298,34 +408,4 @@ backHomeSpec = describe "backHome" $ do
_ -> 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 :: Spec
scrollDownSpec = describe "scrollDown" $
it "should increase the scroll offset" $ let
ps = newProgState & scrollOffset .~ 10
ps' = scrollDown ps
in ps'^.scrollOffset `shouldBe` 11
result^.editorW.to userText `shouldBe` ""

View File

@@ -1,7 +1,7 @@
{-
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -141,9 +141,9 @@ labelTableSpec = describe "labelTable" $
]
expected =
[ " foo: bar"
[ " foo: bar "
, " baz: quux"
, "longer: x"
, "longer: x "
]
in labelTable input `shouldBe` expected

View File

@@ -1,7 +1,7 @@
{-
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -22,7 +22,9 @@ 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 Brick.Types (BrickEvent (VtyEvent))
import Graphics.Vty.Input.Events (Event (EvKey, EvResize), Key (KChar))
import Mtlstats.Handlers
@@ -37,10 +39,18 @@ ynHandlerSpec = describe "ynHandler" $ mapM_
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 )
[ ( "Y pressed", capitalY, Just True )
, ( "y pressed", lowerY, Just True )
, ( "N pressed", capitalN, Just False )
, ( "n pressed", lowerN, Just False )
, ( "x pressed", lowerX, Nothing )
, ( "other event", otherEvent, Nothing )
]
where
capitalY = chE 'Y'
lowerY = chE 'y'
capitalN = chE 'N'
lowerN = chE 'n'
lowerX = chE 'x'
otherEvent = VtyEvent $ EvResize 0 0
chE c = VtyEvent $ EvKey (KChar c) []

View File

@@ -1,7 +1,7 @@
{-
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -22,45 +22,70 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
module Helpers.GoalieSpec (spec) where
import Lens.Micro ((&), (.~), (%~))
import Test.Hspec (Spec, describe, it, shouldBe)
import Test.Hspec (Spec, context, describe, it, shouldBe)
import Mtlstats.Helpers.Goalie
import Mtlstats.Types
spec :: Spec
spec = describe "Goalie"
spec = describe "Goalie" $ do
goalieDetailsSpec
goalieNameSpec
goalieDetailsSpec :: Spec
goalieDetailsSpec = describe "goalieDetails" $ let
input = newGoalie 1 "Joe"
& gRookie .~ True
& gYtd
%~ ( gsGames .~ 2 )
. ( gsMinsPlayed .~ 3 )
. ( gsGoalsAllowed .~ 4 )
. ( gsWins .~ 5 )
. ( gsLosses .~ 6 )
. ( gsTies .~ 7 )
. ( gsShutouts .~ 5 )
. ( gsWins .~ 6 )
. ( gsLosses .~ 7 )
. ( gsTies .~ 8 )
& gLifetime
%~ ( gsGames .~ 8 )
. ( gsMinsPlayed .~ 9 )
. ( gsGoalsAllowed .~ 10 )
. ( gsWins .~ 11 )
. ( gsLosses .~ 12 )
. ( gsTies .~ 13 )
%~ ( gsGames .~ 9 )
. ( gsMinsPlayed .~ 10 )
. ( gsGoalsAllowed .~ 11 )
. ( gsShutouts .~ 12 )
. ( gsWins .~ 13 )
. ( gsLosses .~ 14 )
. ( gsTies .~ 15 )
expected = unlines
[ "Number: 1"
, " Name: Joe"
[ "Number: 1 "
, " Name: Joe*"
, ""
, " YTD Lifetime"
, " Games played 2 8"
, " Mins played 3 9"
, "Goals allowed 4 10"
, " Wins 5 11"
, " Losses 6 12"
, " Ties 7 13"
, " Games played 2 9"
, " Mins played 3 10"
, "Goals allowed 4 11"
, " Shutouts 5 12"
, " Wins 6 13"
, " Losses 7 14"
, " Ties 8 15"
]
in it "should format the output correctly" $
goalieDetails input `shouldBe` expected
goalieNameSpec :: Spec
goalieNameSpec = describe "goalieName" $ mapM_
(\(label, g, expected) -> context label $
it ("should be " ++ expected) $
goalieName g `shouldBe` expected)
-- label, goalie, expected
[ ( "rookie", rookie, "foo*" )
, ( "non-rookie", active, "foo" )
, ( "retired", retired, "*foo" )
]
where
rookie = goalie True True
active = goalie False True
retired = goalie False False
goalie r a = newGoalie 1 "foo"
& gRookie .~ r
& gActive .~ a

View File

@@ -1,7 +1,7 @@
{-
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -22,20 +22,22 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
module Helpers.PlayerSpec (spec) where
import Lens.Micro ((&), (.~))
import Test.Hspec (Spec, describe, it, shouldBe)
import Test.Hspec (Spec, context, describe, it, shouldBe)
import Mtlstats.Helpers.Player
import Mtlstats.Types
spec :: Spec
spec = describe "Player"
spec = describe "Player" $ do
playerDetailsSpec
playerNameSpec
playerDetailsSpec :: Spec
playerDetailsSpec = describe "playerDetails" $
it "should give a detailed description" $ let
p = newPlayer 1 "Joe" "centre"
& pRookie .~ True
& pYtd .~ PlayerStats
{ _psGoals = 2
, _psAssists = 3
@@ -48,8 +50,8 @@ playerDetailsSpec = describe "playerDetails" $
}
expected = unlines
[ " Number: 1"
, " Name: Joe"
[ " Number: 1 "
, " Name: Joe* "
, "Position: centre"
, ""
, " YTD Lifetime"
@@ -59,3 +61,23 @@ playerDetailsSpec = describe "playerDetails" $
]
in playerDetails p `shouldBe` expected
playerNameSpec :: Spec
playerNameSpec = describe "playerName" $ mapM_
(\(label, p, expected) -> context label $
it ("should be " ++ expected) $
playerName p `shouldBe` expected)
-- label, player, expected
[ ( "rookie", rookie, "foo*" )
, ( "non-rookie", nonRookie, "foo" )
, ( "retired", retired, "*foo" )
]
where
rookie = player True True
nonRookie = player False True
retired = player False False
player r a = newPlayer 1 "foo" "centre"
& pRookie .~ r
& pActive .~ a

View File

@@ -0,0 +1,79 @@
{-
mtlstats
Copyright (C) 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 Helpers.PositionSpec (spec) where
import Lens.Micro ((&), (.~))
import Test.Hspec (Spec, context, describe, it, shouldBe)
import Mtlstats.Helpers.Position
import Mtlstats.Types
spec :: Spec
spec = describe "Position" $ do
posSearchSpec
posSearchExactSpec
getPositionsSpec
posSearchSpec :: Spec
posSearchSpec = describe "posSearch" $ mapM_
(\(sStr, expected) -> context ("search string: " ++ show sStr) $
it ("should be " ++ show expected) $
posSearch sStr db `shouldBe` expected)
[ ( "fOo"
, [ ( 2, "foo" )
]
)
, ( "A"
, [ ( 0, "bar" )
, ( 1, "baz" )
]
)
]
posSearchExactSpec :: Spec
posSearchExactSpec = describe "posSearchExact" $ mapM_
(\(input, expected) -> context ("input: " ++ show input) $
it ("should be " ++ show expected) $
posSearchExact input db `shouldBe` expected)
-- input, expected
[ ( "foo", Just 2 )
, ( "FOO", Nothing )
, ( "bar", Just 0 )
, ( "baz", Just 1 )
, ( "a", Nothing )
, ( "quux", Nothing )
]
getPositionsSpec :: Spec
getPositionsSpec = describe "getPositions" $ let
expected = ["bar", "baz", "foo"]
in it ("should be " ++ show expected) $
getPositions db `shouldBe` expected
db :: Database
db = newDatabase & dbPlayers .~
[ newPlayer 2 "Joe" "foo"
, newPlayer 3 "Bob" "bar"
, newPlayer 5 "Bill" "foo"
, newPlayer 8 "Ed" "baz"
]

View File

@@ -1,7 +1,7 @@
{-
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -25,8 +25,10 @@ import Test.Hspec (Spec, describe)
import qualified Helpers.GoalieSpec as Goalie
import qualified Helpers.PlayerSpec as Player
import qualified Helpers.PositionSpec as Position
spec :: Spec
spec = describe "Helper" $ do
Player.spec
Goalie.spec
Position.spec

View File

@@ -1,7 +1,7 @@
{-
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify

View File

@@ -1,7 +1,7 @@
{-
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify

29
test/SpecHelpers.hs Normal file
View File

@@ -0,0 +1,29 @@
{-
mtlstats
Copyright (C) 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 SpecHelpers where
import Brick.Widgets.Edit (Editor, editContentsL, editor)
import Data.Text.Zipper (gotoEOL)
import Lens.Micro ((&), (%~))
mkEditor :: String -> Editor String ()
mkEditor str = editor () (Just 1) str & editContentsL %~ gotoEOL

View File

@@ -1,7 +1,7 @@
{-
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -37,11 +37,11 @@ showSpec :: Spec
showSpec = describe "show" $
it "should display correctly" $ let
menu = Menu "Foo" ()
[ MenuItem '1' "Item 1" $ return ()
, MenuItem '2' "Item 2" $ return ()
[ MenuItem '1' "foo" $ return ()
, MenuItem '2' "bar baz" $ return ()
]
expected =
"Foo\n\
\1) Item 1\n\
\2) Item 2\n"
"Foo\n\n\
\1: foo \n\
\2: bar baz\n"
in show menu `shouldBe` expected

View File

@@ -1,7 +1,7 @@
{-
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -19,7 +19,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
-}
{-# LANGUAGE OverloadedStrings, RankNTypes #-}
{-# LANGUAGE FlexibleInstances, OverloadedStrings, RankNTypes #-}
module TypesSpec
( Comparable (..)
@@ -33,10 +33,11 @@ module TypesSpec
import Control.Monad (replicateM)
import Data.Aeson (FromJSON, ToJSON, decode, encode, toJSON)
import Data.Aeson.Types (Value (Object))
import qualified Data.HashMap.Strict as HM
import qualified Data.Map.Lazy as M
import Data.Ratio ((%))
import qualified GHC.Exts as HM
import Lens.Micro (Lens', (&), (^.), (.~), (?~))
import System.Random (randomRIO)
import System.Random (randomIO, randomRIO)
import Test.Hspec (Spec, context, describe, it, shouldBe)
import Mtlstats.Config
@@ -58,6 +59,8 @@ spec = describe "Mtlstats.Types" $ do
createGoalieStateLSpec
editPlayerStateLSpec
editGoalieStateLSpec
editStandingsModeLSpec
esmSubModeLSpec
teamScoreSpec
otherScoreSpec
homeTeamSpec
@@ -70,6 +73,7 @@ spec = describe "Mtlstats.Types" $ do
gmsPointsSpec
addGameStatsSpec
playerSearchSpec
activePlayerSearchSpec
playerSearchExactSpec
modifyPlayerSpec
playerSummarySpec
@@ -77,6 +81,7 @@ spec = describe "Mtlstats.Types" $ do
psPointsSpec
addPlayerStatsSpec
goalieSearchSpec
activeGoalieSearchSpec
goalieSearchExactSpec
goalieSummarySpec
goalieIsActiveSpec
@@ -191,6 +196,47 @@ editGoalieStateLSpec = describe "editGoalieStateL" $
egs2 = newEditGoalieState
& egsSelectedGoalie ?~ 2
editStandingsModeLSpec :: Spec
editStandingsModeLSpec = describe "editStandingsModeL" $
lensSpec editStandingsModeL
-- getters
[ ( "missing mode", MainMenu, menu )
, ( "with mode", EditStandings home, home )
]
-- setters
[ ( "set mode", MainMenu, home )
, ( "change mode", EditStandings home, away )
]
where
menu = ESMMenu
home = ESMHome ESMSubMenu
away = ESMAway ESMSubMenu
esmSubModeLSpec :: Spec
esmSubModeLSpec = describe "esmSubModeL" $ do
context "getters" $ mapM_
(\(label, mode, expected) -> context label $
it ("should be " ++ show expected) $
mode^.esmSubModeL `shouldBe` expected)
-- label, mode, expected
[ ( "no state", ESMMenu, ESMSubMenu )
, ( "with state", ESMHome ESMEditWins, ESMEditWins )
]
context "setters" $ mapM_
(\(label, mode, expected) -> context label $
it ("should be " ++ show expected) $ let
mode' = mode & esmSubModeL .~ ESMEditWins
in mode' `shouldBe` expected)
-- label, mode, expected
[ ( "no state", ESMMenu, ESMMenu )
, ( "home mode", ESMHome ESMSubMenu, ESMHome ESMEditWins )
, ( "away mode", ESMAway ESMSubMenu, ESMAway ESMEditWins )
]
teamScoreSpec :: Spec
teamScoreSpec = describe "teamScore" $ do
let
@@ -271,6 +317,7 @@ lensSpec lens getters setters = do
player :: Player
player = newPlayer 1 "Joe" "centre"
& pRookie .~ False
& pYtd .~ playerStats 1
& pLifetime .~ playerStats 2
@@ -279,6 +326,8 @@ playerJSON = Object $ HM.fromList
[ ( "number", toJSON (1 :: Int) )
, ( "name", toJSON ("Joe" :: String) )
, ( "position", toJSON ("centre" :: String) )
, ( "rookie", toJSON False )
, ( "active", toJSON True )
, ( "ytd", playerStatsJSON 1 )
, ( "lifetime", playerStatsJSON 2 )
]
@@ -298,6 +347,7 @@ playerStatsJSON n = Object $ HM.fromList
goalie :: Goalie
goalie = newGoalie 1 "Joe"
& gRookie .~ False
& gYtd .~ goalieStats 1
& gLifetime .~ goalieStats 2
@@ -305,6 +355,8 @@ goalieJSON :: Value
goalieJSON = Object $ HM.fromList
[ ( "number", toJSON (1 :: Int) )
, ( "name", toJSON ("Joe" :: String ) )
, ( "rookie", toJSON False )
, ( "active", toJSON True )
, ( "ytd", goalieStatsJSON 1 )
, ( "lifetime", goalieStatsJSON 2 )
]
@@ -597,6 +649,19 @@ playerSearchSpec = describe "playerSearch" $ mapM_
, ( "x", [] )
]
activePlayerSearchSpec :: Spec
activePlayerSearchSpec = describe "activePlayerSearch" $ mapM_
(\(sStr, expected) -> context sStr $
it ("should return " ++ show expected) $ let
ps = [joe, bob, steve & pActive .~ False]
in activePlayerSearch sStr ps `shouldBe` expected)
-- search, result
[ ( "joe", [(0, joe)] )
, ( "o", [(0, joe), (1, bob)] )
, ( "e", [(0, joe)] )
, ( "x", [] )
]
playerSearchExactSpec :: Spec
playerSearchExactSpec = describe "playerSearchExact" $ mapM_
(\(sStr, expected) -> context sStr $
@@ -728,6 +793,28 @@ goalieSearchSpec = describe "goalieSearch" $ do
it "should return Bob" $
goalieSearch "bob" goalies `shouldBe` [result 1]
activeGoalieSearchSpec :: Spec
activeGoalieSearchSpec = describe "activeGoalieSearch" $ do
let
goalies =
[ newGoalie 2 "Joe"
, newGoalie 3 "Bob"
, newGoalie 5 "Steve" & gActive .~ False
]
result n = (n, goalies!!n)
context "partial match" $
it "should return Joe" $
activeGoalieSearch "e" goalies `shouldBe` [result 0]
context "no match" $
it "should return an empty list" $
activeGoalieSearch "x" goalies `shouldBe` []
context "exact match" $
it "should return Bob" $
activeGoalieSearch "bob" goalies `shouldBe` [result 1]
goalieSearchExactSpec :: Spec
goalieSearchExactSpec = describe "goalieSearchExact" $ do
let
@@ -843,6 +930,8 @@ makePlayer = Player
<$> makeNum
<*> makeName
<*> makeName
<*> makeBool
<*> makeBool
<*> makePlayerStats
<*> makePlayerStats
@@ -851,6 +940,8 @@ makeGoalie :: IO Goalie
makeGoalie = Goalie
<$> makeNum
<*> makeName
<*> makeBool
<*> makeBool
<*> makeGoalieStats
<*> makeGoalieStats
@@ -875,6 +966,9 @@ makeGoalieStats = GoalieStats
makeNum :: IO Int
makeNum = randomRIO (1, 10)
makeBool :: IO Bool
makeBool = randomIO
makeName :: IO String
makeName = replicateM 10 $ randomRIO ('A', 'Z')
@@ -944,3 +1038,53 @@ instance Comparable CreateGoalieState where
describe "cgsName" $
it ("should be " ++ expected^.cgsName) $
actual^.cgsName `shouldBe` expected^.cgsName
instance Comparable EditStandingsMode where
compareTest actual expected =
it ("should be " ++ show expected) $
actual `shouldBe` expected
instance Comparable Goalie where
compareTest actual expected = do
describe "gNumber" $
it ("should be " ++ show (expected^.gNumber)) $
actual^.gNumber `shouldBe` expected^.gNumber
describe "gName" $
it ("should be " ++ show (expected^.gName)) $
actual^.gName `shouldBe` expected^.gName
describe "gRookie" $
it ("should be " ++ show (expected^.gRookie)) $
actual^.gRookie `shouldBe` expected^.gRookie
describe "gActive" $
it ("should be " ++ show (expected^.gActive)) $
actual^.gActive `shouldBe` expected^.gActive
describe "gYtd" $
(actual^.gYtd) `compareTest` (expected^.gYtd)
describe "gLifetime" $
(actual^.gLifetime) `compareTest` (expected^.gLifetime)
instance Comparable (M.Map Int GoalieStats) where
compareTest actual expected = do
let
aList = M.toList actual
eList = M.toList expected
it "should have the correct number of elements" $
length aList `shouldBe` length eList
mapM_
(\(n, (ka, va), (ke, ve)) -> context ("element " ++ show n) $ do
context "key" $
it ("should be " ++ show ke) $
ka `shouldBe` ke
context "value" $ va `compareTest` ve)
(zip3 ([0..] :: [Int]) aList eList)

View File

@@ -1,7 +1,7 @@
{-
mtlstats
Copyright (C) 2019 Rhéal Lamothe
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
@@ -26,10 +26,13 @@ import Test.Hspec (Spec, context, describe, it, shouldBe)
import Mtlstats.Util
import SpecHelpers
spec :: Spec
spec = describe "Mtlstats.Util" $ do
nthSpec
modifyNthSpec
dropNthSpec
updateMapSpec
sliceSpec
capitalizeNameSpec
@@ -64,6 +67,20 @@ modifyNthSpec = describe "modifyNth" $ do
it "should not modify the value" $
modifyNth (-1) succ list `shouldBe` [1, 2, 3]
dropNthSpec :: Spec
dropNthSpec = describe "dropNth" $ mapM_
(\(label, n, expected) ->
context label $
it ("should be " ++ show expected) $
dropNth n list `shouldBe` expected)
[ ( "out of bounds", 1, ["foo", "baz"] )
, ( "in bounds", 3, list )
]
where list = ["foo", "bar", "baz"]
updateMapSpec :: Spec
updateMapSpec = describe "updateMap" $ do
let
@@ -99,7 +116,7 @@ capitalizeNameSpec :: Spec
capitalizeNameSpec = describe "capitalizeName" $ mapM_
(\(label, ch, str, expected) -> context label $
it ("should be " ++ expected) $
capitalizeName ch str `shouldBe` expected)
userText (capitalizeName ch $ mkEditor str) `shouldBe` expected)
-- label, character, string, expected
[ ( "initial lower", 'a', "", "A" )
, ( "initial upper", 'A', "", "A" )