A mock-up for a terminal-based spaced repetition learning system

5 min read Original article ↗
#!/usr/bin/env bash # Inspired from [flash.sh](https://github.com/tallguyjenks/fla.sh) # See original license further down # # Copyright © 2024, Lucien Grondin (grondilu on github) # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS X BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # # Original license: # ## MIT License ## ## Copyright (c) 2020 Bryan Jenks ## ## Permission is hereby granted, free of charge, to any person obtaining a copy ## of this software and associated documentation files (the "Software"), to deal ## in the Software without restriction, including without limitation the rights ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ## copies of the Software, and to permit persons to whom the Software is ## furnished to do so, subject to the following conditions: ## ## The above copyright notice and this permission notice shall be included in all ## copies or substantial portions of the Software. ## ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE ## SOFTWARE. set -u declare -r ALPHA=10 declare -ri TIME_SCALE=3600*24*365 declare -ra DECK=( "History:When was the declaration of independence signed?:1776:0" "Math:What is the square root of 4?:2:4" "Science:What is the charge on a proton?:Positive 1:2" "Philosophy:What was Socrates known as?:The Gadfly of Athens:0" "Programming:What is the typical starting index of an array?:0:5" "History:What did Abraham Lincoln typically keep in his hat?:Mail:0" "Math:What is the value of PI to 2 decimal places?:3.14:4" "Science:What is the charge on an electron?:Negative 1:3" "Programming:What does OOP stand for?:Object Oriented Programming:2" "History:What did Socrates drink to commit suicide?:Hemlock Tea:2" "Math:What is the general equation for the slope of a line?:y=mx+b:4" "Science:What is the charge on a neutron?:Neutral:1" "Programming:What is Vim?:God's Text Editor:999" "History:What were the British known by during the American Revolution?:The Redcoats:1" "Math:What is the value of this equation - log10(100)?:2:0" "Science:What are protons and neutrons made of?:quarks:5" "Programming:What does RAM stand for?:Random Access Memory:1" "History:What was the year 2000 also known as?:Y2K:0" "Math:What is the formula for the mean?:Sum/count:4" "Science:What is cold?:The absence of heat:3" "Programming:What languages are the worst?:Proprietary:999" "History:When did man land on the moon?:1969:4" "Math:10^3=?:1000:1" "Science:The _____ ______ Project mapped all of man's genes.:Human Genome:3" "Programming:What is the best computer to program on?:Thinkpad:999" "History:When was fla.sh created?:April 2020:999" "Math:What do you call a number only divisible by itself and 1?:Prime:0" "Science:What is the distance between the Earth and Sol called?:An Astronomical Unit (AU):1" "Programming:What is the best operating system?:Arch, because BTW i run Arch:999" ) declare -a LOG=() center() { local line="$1" echo "$line" | sed $'s/\e\[[0-9;]*m//g' | { read -r echo -e "\e[$(( (${COLUMNS:-$(tput cols)}-${#REPLY})/2 + 1))G$line" } } softmax() { local alpha="${1:-1.0}" local -i limit=50 awk -v alpha="$alpha" -v limit=$limit -e '{ x = alpha*$1; sum += e[$2] = exp(x<-limit ? -limit : (x > limit ? limit : x)) } END { for (i in e) { print e[i]/sum, i } }' } if ((${#LOG[@]} == 0)) then # Log is empty, so to get started # we'll fill it with failed attempts an hour ago. declare -i i timestamp=$(date +%s)-3600 for ((i=0;i<${#DECK[@]};i++)) do LOG+=("$timestamp:$timestamp:$i:0") done fi # hiding the cursor echo -ne "\e[?25l" declare line while for line in "${LOG[@]}" do echo "$line" done | awk -F: -v now=$(date +%s) -v tScale=$TIME_SCALE -e '{ deltaT = now - $1; if ($4) { s[$3]=-deltaT/tScale } else { if (s[$3] < 0) { s[$3]=tScale/deltaT } else { s[$3]+=tScale/deltaT } } } END { for (i in s) print +s[i], i }' | softmax "-$ALPHA" | LANG=C sort -t' ' -g -k1 | awk -v x="$((RANDOM%32768))" -e 'BEGIN { x /= 32768 } { if ( (s+=$1) > x ) { print $2; found=1; exit } } END { if (found==0) { print $2 } } ' | { declare -i line_choice read -r line_choice declare -a record IFS=: read -ra record <<<"${DECK[line_choice]}" declare -i t1=$(date +%s) width (( width=${COLUMNS:-$(tput cols)}, width=width < 80 ? width : 80 )) clear echo -n "${record[1]}" | fmt -w $width | { mapfile -t echo -ne "\e[H" echo -ne "\e[$(( (${LINES:-$(tput lines)}-${#MAPFILE[@]})/2))B" for line in "${MAPFILE[@]}" do center "$line" done read -sn1 </dev/tty echo -ne "\e[1A\e[2J" declare -i t2=$(date +%s) case "$REPLY" in $' ') center $'\e[31m'"${record[2]}"$'\e[0m' LOG+=("$t1:$t2:$line_choice:1") sleep 1 ;; $'') center $'\e[32m'"${record[2]}"$'\e[0m' LOG+=("$t1:$t2:$line_choice:0") sleep .2 ;; *) # show the cursor echo -e "\e[?25h" exit 1 esac } } do sleep .01 done