-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtimer.elm
230 lines (196 loc) · 6.74 KB
/
timer.elm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
port module Timer exposing (main, setStorage, scrollBottom, scrambleReq, scrambleRes)
import Html exposing (Html, button, div, text, h1, ul, li, a, span, header, section, select, optgroup, option)
import Html.App as Html
import Html.Lazy as Html
import Html.Attributes exposing (class, attribute, href, title, value, selected)
import Time exposing (Time, second)
import Task
import Keyboard
import Scrambles
import History
import Util exposing ((<$>), pretty, toPrecision)
port setStorage : SerialModel -> Cmd msg
port scrollBottom : String -> Cmd msg
port scrambleReq : Scrambles.ScrType -> Cmd msg
port scrambleRes : (String -> msg) -> Sub msg
main : Program (Maybe SerialModel)
main =
Html.programWithFlags
{ init = init
, view = view
, update = update
, subscriptions = subscriptions }
-- MODEL
-- waiting is a hack for the time between keyup and keydown, really
type TimerState = Stopped | Running | Inspecting | Waiting
type alias SerialModel = {
oldTimes : History.SerialModel
, scrType : Scrambles.SerialModel
}
serialize : Model -> SerialModel
serialize m = SerialModel (History.serialize m.history) (Scrambles.serialize m.scramble)
type alias Model = {
time : Time,
startTime : Time,
state : TimerState,
totalInspection : Time,
scramble : Scrambles.Model,
history : History.Model,
inspectionTime : Time
}
deserialize : SerialModel -> Model
deserialize st = Model 0 0 Stopped (15 * second) (Scrambles.deserialize st.scrType) (History.deserialize st.oldTimes) 0
empty : SerialModel
empty = SerialModel History.empty Scrambles.empty
init : Maybe SerialModel -> (Model, Cmd Msg)
init local_storage =
let
stored : SerialModel
stored = Maybe.withDefault empty local_storage
in
withGetScramble (deserialize stored, Cmd.none)
withCmd : (Model -> Cmd Msg) -> (( Model, Cmd Msg ) -> ( Model, Cmd Msg ))
withCmd makeCmd (model, cmds) = (model, Cmd.batch [makeCmd model, cmds])
withSetStorage : ( Model, Cmd Msg ) -> ( Model, Cmd Msg )
withSetStorage = withCmd (setStorage << serialize)
withGetScramble : (Model, Cmd Msg) -> (Model, Cmd Msg)
withGetScramble = withCmd (\model -> scrambleReq model.scramble.scrtype )
-- UPDATE
type UpDown = Up | Down
type Msg
= NoOp
| Tick Time
| StartTime Time
| Space UpDown
| HistoryMsg History.Msg
| ScrambleMsg Scrambles.Msg
getCurTime : (Time -> Msg) -> Cmd Msg
getCurTime m = Task.perform m m Time.now
addOldTime : Model -> Model
addOldTime m =
updateHistory m <| History.addTime {
time = m.time,
startTime = m.startTime,
scramble = m.scramble,
inspectTime = m.inspectionTime
} m.history
scrollOldTimes : Cmd Msg
scrollOldTimes = scrollBottom ".oldtimes"
const : a -> b -> a
const a b = a
updateHistory : Model -> History.Model -> Model
updateHistory m newhist = {m | history = newhist}
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
let
upM m = (m, Cmd.none)
donothing = upM model
in
case msg of
NoOp -> donothing
Tick t ->
upM { model | time = t - model.startTime }
StartTime t ->
upM { model | time = 0, startTime = t, state = Inspecting}
Space Down ->
case model.state of
Running ->
withGetScramble <| withSetStorage <| (addOldTime { model | state = Waiting }, scrollOldTimes)
Inspecting ->
upM { model | state = Running, startTime = model.time + model.startTime, time = 0, inspectionTime = model.time }
_ -> donothing
Space Up ->
case model.state of
Stopped -> (model, getCurTime StartTime)
Waiting -> upM {model | state = Stopped }
_ -> donothing
HistoryMsg msg ->
let
(newhist, histmsg) = History.update msg model.history
in
withSetStorage <| ({model | history = newhist}, Cmd.map HistoryMsg histmsg)
ScrambleMsg msg ->
let
(newscrmbl, shouldupdate) = Scrambles.update msg model.scramble
in
if shouldupdate then
withSetStorage <| withGetScramble <| upM {model | scramble = newscrmbl}
else upM {model | scramble = newscrmbl}
keyupdown : UpDown -> Int -> Msg
keyupdown updown keycode =
if keycode == 32
then Space updown
else NoOp
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions m =
let
shouldTick = ((m.state == Running) || (m.state == Inspecting))
in
Sub.batch
[ scrambleRes (ScrambleMsg << Scrambles.newscramble)
, Keyboard.downs (keyupdown Down)
, Keyboard.ups (keyupdown Up)
, (if shouldTick then
Time.every (second / 100) Tick
else Sub.none)
]
-- VIEW
getRunTime : Model -> Float
getRunTime m = (toFloat <| round (m.time / 10)) / 100
getInspectTime : Model -> String
getInspectTime m =
let
curtime = m.totalInspection - m.time
in
if curtime < (-2 * second) then "DNF"
else if curtime < 0 then "+2"
else toString <| ceiling (curtime / 1000)
viewStats : History.Model -> Html Msg
viewStats history =
let
renderTimeSD : (Time, Float) -> String
renderTimeSD (t, sd) = (pretty t) ++ " (σ=" ++ (toPrecision 2 sd) ++ ")"
st = History.getStats history
stat : String -> String -> Html Msg
stat label content = div [ class "stat"] [
span [class "stat_left"] [text label], span [] [text content]]
statSec : String -> List (String, String) -> Html Msg
statSec head rows = div [class "stat_group"]
([ div [ class "stat_hdr" ] [text head] ] ++
List.map (uncurry stat) rows)
mapAvg : (Int, (Time, Float), (Time, Float)) -> Html Msg
mapAvg (avgn, cur, best) =
statSec ("avg" ++ toString avgn)
[ ("current", renderTimeSD cur)
, ("best", renderTimeSD best)]
in
section [ class ("stats") ]
([ header [] [text <| "stats (" ++ (toString st.totalFin) ++ "/" ++ (toString st.total) ++ ")"]
, statSec "times"
(List.filterMap identity
[ ((,) "best" << pretty) <$> st.best
, ((,) "worst" << pretty) <$> st.worst
, ((,) "average" << renderTimeSD) <$> st.average
, ((,) "mean" << pretty) <$> st.mean
])
] ++ (List.map mapAvg st.averages))
view : Model -> Html Msg
view model = let
timefmt = case model.state of
Stopped -> if model.startTime == 0 then "Ready" else pretty model.time
Inspecting -> getInspectTime model
_ -> pretty model.time
inspectingpenalty = model.state == Inspecting && model.time > 15 * second
stateToClassName = case model.state of
Stopped -> "stopped"
Inspecting -> "inspecting" ++ (if inspectingpenalty then " penalty" else "")
Running -> "running"
Waiting -> "stopped"
in
div [class "container"]
[ Html.map ScrambleMsg <| Html.lazy Scrambles.view model.scramble
, section [ class ("timerstate " ++ stateToClassName) ] [ h1 [] [ text timefmt ] ]
, Html.map HistoryMsg <| Html.lazy History.view model.history
, Html.lazy viewStats model.history
]