Last active
July 11, 2022 07:51
-
-
Save duruyao/e6a66acfab490b4a6e3fa9c3c12f088a to your computer and use it in GitHub Desktop.
Command line game for typing practice.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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