Skip to content

Instantly share code, notes, and snippets.

@duruyao
Last active July 11, 2022 07:51
Show Gist options
  • Save duruyao/e6a66acfab490b4a6e3fa9c3c12f088a to your computer and use it in GitHub Desktop.
Save duruyao/e6a66acfab490b4a6e3fa9c3c12f088a to your computer and use it in GitHub Desktop.
Command line game for typing practice.
#!/usr/bin/env python3
# date: 2022-07-08
# author: duruyao@gmail.com
# desc: command line game for typing practice
import os
import sys
import time
import random
import string
import getopt
import pathlib
import datetime
def info_ln(values):
print('\033[1;32;32m', values, '\033[m', sep='', end='\n')
def trace_ln(values):
print('\033[1;32;34m', values, '\033[m', sep='', end='\n')
def warning_ln(values):
print('\033[1;32;33m', values, '\033[m', sep='', end='\n')
def error_ln(values):
print('\033[1;32;31m', values, '\033[m', sep='', end='\n')
def show_usage():
"""Usage: python3 {} [OPTIONS]
Command line game for typing practice
Options:
-b, --batch=<N> Set batch size (default: 100)
-d, --diff=<easy|normal|hard> Select game difficulty (default: easy)
-l, --length=<N> Set length of random string (default: 1)
-h, --help Display this help message
Examples:
python3 {}
python3 {} -l 1 -b 100 -d easy
python3 {} --length=1 --batch=100 --diff=easy
"""
script = os.path.realpath(sys.argv[0])
print(show_usage.__doc__.format(script, script, script, script))
def rand_str(chars=string.ascii_lowercase + ',./;', length=10, sep=''):
return sep.join(random.choice(chars) for _ in range(length))
def main():
diff = 'easy'
length = 1
batch = 100
thesaurus = {'easy': string.ascii_lowercase + ',./;',
'normal': string.ascii_letters + string.digits + ',./;',
'hard': string.printable}
try:
opts, args = getopt.getopt(sys.argv[1:], "b:d:l:h", ["batch=", "diff=", "length=", "help"])
except getopt.GetoptError as e:
error_ln(e)
show_usage()
sys.exit(1)
for opt_key, opt_value in opts:
if opt_key in ('-b', '--batch'):
batch = int(opt_value)
elif opt_key in ('-d', '--diff'):
diff = str(opt_value)
elif opt_key in ('-l', '--length'):
length = int(opt_value)
elif opt_key in ('-h', '--help'):
show_usage()
sys.exit(0)
level = '{}-{}-{}'.format(diff, length, batch)
history_dir = '{}/.qwer'.format(os.path.expanduser('~'))
history_file = '{}/{}.md'.format(history_dir, level)
pathlib.Path(history_dir).mkdir(parents=True, exist_ok=True)
if not os.path.exists(history_file):
with open(history_file, 'w') as file:
file.write('| SCORE | LEVEL | DATE | GAMER |\n')
file.write('|--------|----------------|---------------------|------------------|\n')
gain = 0
loss = 0
gamer = 'someone'
for i in 3, 2, 1, 'Go':
print('{:<2}!'.format(i), sep='')
time.sleep(1)
print()
for i in range(batch):
print('[{:>3}]'.format(i + 1))
word = rand_str(chars=thesaurus[diff], length=length)
answer = ''
begin = datetime.datetime.now()
while answer != word:
print(' Q: ', end='')
trace_ln(word)
print(' A: ', end='')
answer = input()
end = datetime.datetime.now()
duration = end - begin
print(' T: ', end='')
if duration.total_seconds() < length + 1:
info_ln(duration)
gain += 1
else:
warning_ln(duration)
loss += 1
score = '%.2f' % ((gain * 100) / (gain + loss))
print()
now = datetime.datetime.now()
date = '{:>4}-{:0>2}-{:0>2} {:0>2}:{:0>2}:{:0>2}'. \
format(now.year, now.month, now.day, now.hour, now.minute, now.second)
print('SCORE: %s' % score)
print('YOUR NAME: ', end='')
name = input()
if len(name):
gamer = name[:16]
print('HISTORY: ')
with open(history_file, 'r') as file:
lines = file.readlines()
lines.append('| {:0>6} | {:<14} | {:<19} | {:<16} | <-\n'.format(score, level, date, gamer))
for line in lines[:2]:
print(' ' + line, end='')
lines[:] = lines[2:]
# lines.remove()
lines.sort(reverse=True)
for line in lines:
print(' ' + line, end='')
with open(history_file, 'a') as file:
file.write('| {:0>6} | {:<14} | {:<19} | {:<16} |\n'.format(score, level, date, gamer))
if __name__ == '__main__':
main()
#!/usr/bin/env bash
## date: 2022-07-07
## author: duruyao@gmail.com
## desc: command line game for typing practice
function info_ln() {
printf "\033[1;32;32m%s\n\033[m" "${1}"
}
function trace_ln() {
printf "\033[1;32;34m%s\n\033[m" "${1}"
}
function warning_ln() {
printf "\033[1;32;33m%s\n\033[m" "${1}"
}
function error_ln() {
printf "\033[1;32;31m%s\n\033[m" "${1}"
}
function sec2hms() {
printf "%d:%02d:%02d" "$((10#$1 / 3600))" "$((10#$1 / 60 % 60))" "$((10#$1 % 60))"
}
function show_usage() {
cat <<EOF
Usage: bash ${exec} [OPTIONS]
Command line game for typing practice
Options:
-b, --batch=<N> Set batch size (default: 100)
-d, --diff=<easy|normal|hard> Select game difficulty (default: easy)
-l, --length=<N> Set length of random string (default: 1)
-h, --help Display this help message
Examples:
bash ${exec}
bash ${exec} -l 1 -b 100 -d easy
bash ${exec} --length=1 --batch=100 --diff=easy
EOF
}
exec="$(realpath "$0")"
diff="easy"
length=1
batch=100
thesaurus=(
"a-z,./;"
"A-Za-z0-9,./;"
"A-Za-z0-9,./;!\"#$%&\'()*+-:<=>?@[\\\\]^_\`{|}~"
)
chars="${thesaurus[0]}"
while (($#)); do
case "$1" in
-b | --batch)
if [ -z "$2" ] || ! [[ $2 =~ ^[0-9]+$ ]]; then
error_ln "Error: '$1' requires an integer argument" >&2
show_usage >&2
exit 1
fi
batch=$2
shift 2
;;
--batch=?*)
if ! [[ ${1#*=} =~ ^[0-9]+$ ]]; then
error_ln "Error: '$1' requires an integer argument" >&2
show_usage >&2
exit 1
fi
batch=${1#*=}
shift 1
;;
-d | --diff)
if [ -z "$2" ] || ! [[ $2 =~ ^(EASY|NORMAL|HARD|easy|normal|hard)$ ]]; then
error_ln "Error: '$1' requires a known argument (easy|normal|hard)" >&2
show_usage >&2
exit 1
fi
diff="$2"
shift 2
;;
--diff=?*)
if ! [[ "${1#*=}" =~ ^(EASY|NORMAL|HARD|easy|normal|hard)$ ]]; then
error_ln "Error: Unknown argument: '$2'" >&2
show_usage >&2
exit 1
fi
diff="${1#*=}"
shift 1
;;
-l | --length)
if [ -z "$2" ] || ! [[ $2 =~ ^[0-9]+$ ]]; then
error_ln "Error: '$1' requires an integer argument" >&2
show_usage >&2
exit 1
fi
length=$2
shift 2
;;
-l=?* | --length=?*)
if ! [[ ${1#*=} =~ ^[0-9]+$ ]]; then
error_ln "Error: '$1' requires an integer argument" >&2
show_usage >&2
exit 1
fi
length=${1#*=}
shift 1
;;
-h | --help)
show_usage
exit 0
;;
--* | -* | *)
error_ln "Error: Unknown flag: '$1'" >&2
show_usage >&2
exit 1
;;
esac
done
if [ "${diff}" == "normal" ]; then
chars="${thesaurus[1]}"
elif [ "${diff}" == "hard" ]; then
chars="${thesaurus[2]}"
fi
level="${diff}-${length}-${batch}"
history="${HOME}/.qwer/${level}.md"
mkdir -p "$(dirname "${history}")"
if ! [ -f "${history}" ]; then
{
printf "%s\n" "| SCORE | LEVEL | DATE | GAMER |"
printf "%s\n" "|--------|----------------|---------------------|------------------|"
} >>"${history}"
fi
gain=0
loss=0
gamer="someone"
for i in 3 2 1 Go; do
printf "%-2s!\n" "${i}"
sleep 1
done
echo
for ((cnt = 1; cnt <= "${batch}"; cnt++)); do
word="$(LC_ALL=C tr -dc "${chars}" </dev/urandom | head -c "${length}")"
printf "[%3s]\n" "${cnt}"
input=""
begin="$(TZ=UTC-8 date "+%Y-%m-%d %H:%M:%S")"
while [ "${input}" != "${word}" ]; do
printf " Q: "
trace_ln "${word}"
printf " A: "
read -r input
done
end="$(TZ=UTC-8 date "+%Y-%m-%d %H:%M:%S")"
duration="$(("$(date +%s -d "${end}")" - "$(date +%s -d "${begin}")"))"
printf " T: "
if [ ${duration} -gt "${length}" ]; then
warning_ln "$(sec2hms "${duration}")"
loss=$((loss + 1))
else
info_ln "$(sec2hms "${duration}")"
gain=$((gain + 1))
fi
done
score="$(echo "(${gain}*100)/(${gain}+${loss})" | bc -l)"
printf "\n"
printf "SCORE: %.2f\n" "${score}"
printf "YOUR NAME: "
read -r name
if [ -n "${name}" ]; then
gamer="$(echo "${name}" | head -c 16)"
fi
date="$(TZ=UTC-8 date "+%Y-%m-%d %H:%M:%S")"
temp="/tmp/$(date | md5sum | head -c 32).tmp"
cp "${history}" "${temp}"
printf "| %06.2f | %-14s | %-19s | %-16s | <-\n" "${score}" "${level}" "${date}" "${gamer}" >>"${temp}"
echo "HISTORY:"
awk "NR <=2" "${temp}" | sed "s/^/ /g"
awk "NR >=3" "${temp}" | sed "s/^/ /g" | sort -r
rm -f "${temp}"
printf "| %06.2f | %-14s | %-19s | %-16s |\n" "${score}" "${level}" "${date}" "${gamer}" >>"${history}"
echo ""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment