• R/O
  • HTTP
  • SSH
  • HTTPS

Commit

Tags
Aucun tag

Frequently used words (click to add to your profile)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

A Nix-friendly SQLite-enhanced fork of Flitter, a speedrunning split timer for Unix-style terminals


Commit MetaInfo

Révision8ef92b30e9d6b6f99d878d198a107b8d76885cc8 (tree)
l'heure2022-12-20 08:55:14
AuteurCorbin <cds@corb...>
CommiterCorbin

Message de Log

Add command for summarizing splits.

So that I don't have to run the entire timer just to look at a single
split file.

Change Summary

Modification

--- a/bin/dune
+++ b/bin/dune
@@ -1,9 +1,6 @@
11 (executables
2- (names main)
2+ (names main summary_main)
3+ (public_names flitter flitter-summary)
34 (libraries flitter))
45
5-(install
6- (section bin)
7- (files (main.exe as flitter)))
8-
96 ; vim:ft=scheme
--- /dev/null
+++ b/bin/summary_main.ml
@@ -0,0 +1,10 @@
1+let usage =
2+ "Usage:\n" ^ "flitter-summary <splits_path>\n"
3+ ^ "Summarize the splits file pointed to by `splits_path`.\n"
4+
5+let () =
6+ match Sys.argv with
7+ | [| _; path |] ->
8+ let summary = Loadsave.load_summary path in
9+ Summary.print_summary summary
10+ | _ -> print_string usage
--- a/shell.nix
+++ b/shell.nix
@@ -7,6 +7,6 @@ in pkgs.stdenv.mkDerivation {
77 # working with S-expressions
88 ocamlPackages.sexp
99 # maintaining OCaml code
10- ocamlformat
10+ ocamlformat ocamlPackages.utop
1111 ];
1212 }
--- a/src/display.mli
+++ b/src/display.mli
@@ -2,4 +2,4 @@ type t
22
33 val make : unit -> t
44 val draw : t -> Timer_types.timer -> unit
5-val close : t -> unit
\ No newline at end of file
5+val close : t -> unit
--- a/src/duration.mli
+++ b/src/duration.mli
@@ -4,11 +4,9 @@ type t = int (* Milliseconds *)
44
55 val t_of_sexp : Sexp.t -> t
66 val sexp_of_t : t -> Sexp.t
7-
87 val of_string : string -> t option
98 val to_string : t -> int -> string (* int is # of decimal places *)
109 val to_string_plus : t -> int -> string
1110 val string_valid : string -> bool
12-
1311 val between : float -> float -> t
1412 val since : float -> t
--- a/src/event_loop.mli
+++ b/src/event_loop.mli
@@ -1,4 +1,4 @@
11 type t
22
33 val make : Timer_types.timer -> t Lwt.t
4-val loop : t -> unit Lwt.t
\ No newline at end of file
4+val loop : t -> unit Lwt.t
--- a/src/hotkeys.mli
+++ b/src/hotkeys.mli
@@ -1,4 +1,4 @@
11 type keypress = float * string
22 type t = keypress Lwt_stream.t
33
4-val make_stream : unit -> t Lwt.t
\ No newline at end of file
4+val make_stream : unit -> t Lwt.t
--- a/src/loadsave.ml
+++ b/src/loadsave.ml
@@ -12,6 +12,12 @@ type split = {
1212 type gold = { title : string; duration : Duration.t } [@@deriving sexp]
1313 type archived_run = { attempt : int; splits : split array } [@@deriving sexp]
1414
15+let time_of_split segment { splits } =
16+ Array.find_map splits ~f:(fun { title; time } ->
17+ if String.equal segment title then Some time else None)
18+
19+let times_for_segment segment = List.filter_map ~f:(time_of_split segment)
20+
1521 type game = {
1622 title : string;
1723 category : string;
@@ -55,6 +61,18 @@ let game_of_sexp sexp =
5561 "Not all history runs have same number of splits as split_names" sexp
5662 else game
5763
64+let relativize run =
65+ let rel =
66+ Array.folding_map run.splits ~init:0 ~f:(fun offset sp ->
67+ (sp.time, { sp with time = sp.time - offset }))
68+ in
69+ { run with splits = rel }
70+
71+let times_for_game_history { split_names; history } =
72+ let relative_history = List.map ~f:relativize history in
73+ Array.map split_names ~f:(fun title ->
74+ (title, times_for_segment title relative_history))
75+
5876 let load_golds parsed_game =
5977 if Array.length parsed_game.golds = 0 then
6078 Array.map parsed_game.split_names ~f:(fun name ->
@@ -99,6 +117,20 @@ let load filepath =
99117 splits_file = filepath;
100118 }
101119
120+let load_summary filepath =
121+ let game = Sexp.load_sexp_conv_exn filepath game_of_sexp in
122+ (* let pb = load_run_opt game.personal_best in
123+ let golds = load_golds game in *)
124+ let times = times_for_game_history game in
125+
126+ {
127+ Summary.title = game.title;
128+ category = game.category;
129+ attempts = game.attempts;
130+ completed = game.completed;
131+ splits = times;
132+ }
133+
102134 let export_run (run : Timer_types.archived_run) =
103135 {
104136 attempt = run.attempt;
--- a/src/loadsave.mli
+++ b/src/loadsave.mli
@@ -1,2 +1,6 @@
1+(* Load and prepare to run. *)
12 val load : string -> Timer_types.timer
2-val save : Timer_types.timer -> unit
\ No newline at end of file
3+val save : Timer_types.timer -> unit
4+
5+(* Load and summarize. *)
6+val load_summary : string -> Summary.t
--- /dev/null
+++ b/src/summary.ml
@@ -0,0 +1,52 @@
1+open Core
2+
3+type welford = { n : int; m1 : float; m2 : float }
4+
5+let do_welford =
6+ List.fold
7+ ~f:(fun { n; m1; m2 } d ->
8+ let delta = Float.of_int d -. m1 in
9+ let dn = delta /. Float.of_int (n + 1) in
10+ let t = delta *. dn *. Float.of_int n in
11+ { n = n + 1; m1 = m1 +. dn; m2 = m2 +. t })
12+ ~init:{ n = 0; m1 = 0.0; m2 = 0.0 }
13+
14+let mean { m1; _ } = m1
15+
16+let variance { n; m2; _ } =
17+ if n < 2 then None else Some (m2 /. Float.of_int (n - 1))
18+
19+let stddev s = Option.value_map (variance s) ~default:0.0 ~f:Float.sqrt
20+let best = List.fold ~f:Int.min ~init:Int.max_value
21+
22+let print_summary_segment width segment times =
23+ let w = do_welford times in
24+ (* NB: 12 digits is a reasonable width for durations; it would take over a
25+ week for a segment to surpass it! *)
26+ Printf.printf "%*s: %12s %12s %12s (best/mean/stddev)\n" (width + 2) segment
27+ (Duration.to_string (best times) 3)
28+ (Duration.to_string (Float.to_int (mean w)) 3)
29+ (Duration.to_string (Float.to_int (stddev w)) 3)
30+
31+type t = {
32+ title : string;
33+ category : string;
34+ attempts : int;
35+ completed : int;
36+ splits : (string * Duration.t list) array;
37+}
38+
39+let print_completion completed attempts =
40+ let percentage = completed * 100 / attempts in
41+ Printf.printf "Completion rate: %d/%d (%d%%)\n" completed attempts percentage
42+
43+let print_summary (summary : t) =
44+ let titlewidth =
45+ Array.fold summary.splits ~init:0 ~f:(fun x (title, _) ->
46+ Int.max x (String.length title))
47+ in
48+ print_endline ("Title: " ^ summary.title);
49+ print_endline ("Category: " ^ summary.category);
50+ print_completion summary.completed summary.attempts;
51+ Array.iter summary.splits ~f:(fun (title, times) ->
52+ print_summary_segment titlewidth title times)