-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsm2
executable file
·160 lines (127 loc) · 3.57 KB
/
sm2
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
#!/bin/sh -e
help() {
cat - >&2 <<EOF
sm2 [-f] deck
The deck is a text file that will be modified by the program.
A card is a line of the deck with the form:
front<delimiter>back
Front is the text shown to the user and back is the text the user
intends to remember (e.g. 2+2=4).
The delimiter can be a TAB, =, COMMA, or SPACE (checked in that
order). sm2 will automatically convert each card into a TAB-delimited
format.
The -f flag tells sm2 to show all cards, regardless of when they're
meant to be shown (useful for when you've run out of cards to view
for the day).
For more info on this algorithm, see:
https://www.supermemo.com/en/archives1990-2015/english/ol/sm2
EOF
}
TAB="$(printf '\t')"
BAR='----------------'
EPOCH="$(awk 'BEGIN{srand();print srand()}')"
unset -v force
while getopts 'f' o; do
case "$o" in
f) force=1 ;;
h) help; exit 0 ;;
*) help; exit 1 ;;
esac
done
shift $((OPTIND-1))
: "${1:?No deck provided, nothing for me to do!}"
deck=$(shuf < "$1" | grep .)
done=
# --- MADNESS BEGINS --- #
remaining=$(wc -l) <<-EOF
$deck
EOF
remaining=$((remaining+1))
linedone() {
done="${done:+$done
}$(printf '%s\t%s\t%s\t%s\t%s\n' "$recalls" "$difficulty" "$date" "$front" "$back")"
}
trap 'trap - EXIT TERM QUIT HUP INT ABRT; done="${done:+$done
}$(tail -n "$remaining")" <<-EOF
$deck
EOF
printf '"'%s\n'"' "$done" > "$1"' EXIT TERM QUIT HUP INT ABRT
# --- MADNESS ENDS --- #
# If you want to compare to the algorithm in the link above:
# recalls = n, difficulty = EF, days = I, grade = q
while IFS="$TAB" read -r recalls difficulty date front back <&4; do
remaining=$((remaining-1))
# handle missing fields
if [ ! "$back" ]; then
if [ ! "$difficulty" ]; then
[ "$recalls" ] || continue # skip blank cards
case "$recalls" in # handle alternative delimiters
*=*) difficulty=${recalls#*=}; recalls=${recalls%%=*} ;;
*,*) difficulty=${recalls#*,}; recalls=${recalls%%,*} ;;
*' '*) difficulty=${recalls#*\ }; recalls=${recalls%%\ *} ;;
*) echo "Can't determine delimiter for: $recalls" >&2; exit 1 ;;
esac
# correct the fields, set missing fields to default values
front="$recalls"; back="$difficulty";
recalls=0; difficulty=2.5; date=0; days=0;
fi
fi
if [ "$date" ]; then
days=$(bc -l) <<-EOF
($date - $EPOCH) / 86400
EOF
fi
# skip cards that are too soon to be viewed
if [ ! "$force" ] && expr "$date" '>' 0 && expr "$date" '<' "$EPOCH"; then
linedone
continue
fi
printf '%s\n> ' "$front"
read -r response
case "$response" in
"$back") printf "\n$BAR\nCorrect!\n$BAR\n\n" ;;
*) printf "\n$BAR\nThe correct answer is %s\n$BAR\n\n" "$back" ;;
esac
grade() {
cat - <<-EOF
Select a grade:
(0) Incorrect, complete failure to recall
(1) Incorrect, but answer seems familiar
(2) Incorrect, but answer seems easy to remember
(3) Correct, but difficult to recall
(4) Correct, but some hesitation
(5) Correct, perfect recall
EOF
read -r grade || grade=
case "$grade" in
[3-5]) # correct
case "$recalls" in
0) days=1 ;; 1) days=6 ;;
*)
days=$(bc -l) <<-EOF
$days*$difficulty
EOF
;;
esac
if [ "$grade" -ne 4 ]; then
difficulty=$(bc -l) <<-EOF
$difficulty-0.8+0.28*$grade-0.02*$grade*$grade
EOF
fi
if expr "$difficulty" '<' 1.3 > /dev/null; then difficulty=1.3; fi
recalls=$((recalls+1))
;;
[0-2]) # incorrect
recalls=0; days=1
;;
*) return 1 ;;
esac
}
while ! grade; do :; done # retries if the user provides bad input
date=$(bc -l) <<-EOF
$days * 86400 + $EPOCH
EOF
linedone
done 4<<-EOF
$deck
EOF