Compare commits

..
Author SHA1 Message Date
adam 39265c3c9d resume on correct row after save during row reveal 2025-05-04 12:33:56 +01:00
+42 -92
View File
@@ -33,7 +33,7 @@ function main() {
declare -g ANSWER declare -g ANSWER
declare -g POOL='QWERTYUIOPASDFGHJKLZXCVBNM' declare -g POOL='QWERTYUIOPASDFGHJKLZXCVBNM'
declare -Ag YELLOWS declare -Ag YELLOWS
declare -Ag KEYS declare -g REVEALING=0
ANSWER=$(select_random_word) ANSWER=$(select_random_word)
set_options "$@" set_options "$@"
@@ -51,31 +51,24 @@ function main() {
init_board init_board
fi fi
[[ "${ONLINE}" == 1 ]] && save_game "${ROW}" [[ "${ONLINE}" == 1 ]] && save_game "NO_EXIT"
render_board render_board
for (( ROW; ROW<HEIGHT; ROW++ )); do for (( ROW; ROW<HEIGHT; ROW++ )); do
declare GUESS declare GUESS
[[ "${KEYBOARD}" == 1 ]] && render_keyboard [[ "${KEYBOARD}" == 1 ]] && render_keyboard
GUESS=$(retrieve_guess) || { echo -en "${NEWLINE}"; exit 0; } GUESS=$(retrive_guess)
check_guess "${GUESS}" "${ROW}" ; WIN=$? check_guess "${GUESS}" "${ROW}" ; WIN=$?
# Save before the reveal so quitting or disconnecting can't undo a guess
if [[ $WIN == 5 ]] || (( ROW+1 == HEIGHT )); then
save_result
elif [[ "${ONLINE}" == 1 ]]; then
save_game "$(( ROW+1 ))"
fi
reveal_row "${ROW}" reveal_row "${ROW}"
REVEALING=0
if [[ $WIN == 5 ]]; then if [[ $WIN == 5 ]]; then
save_result
echo -e "${CLEAR_DOWN}YOU WIN" echo -e "${CLEAR_DOWN}YOU WIN"
if [[ "${ONLINE}" == 1 && "${DAILY_DONE}" != 1 ]]; then if [[ "${ONLINE}" == 1 && "${DAILY_DONE}" != 1 ]]; then
echo "Share/view your game with -> curl \"${URL_BASE}/${RESULT_FILE}\"" echo "Share/view your game with -> curl \"${URL_BASE}/${RESULT_FILE}\""
echo "Share/view your stats with -> curl \"${URL_BASE}/stats/${USER}\""
echo "Check out the new homepage in your browser -> ${URL_BASE}"
while read -r board_line; do echo "${board_line//[A-Z]/ }"; done < "${RESULT_FILE}" while read -r board_line; do echo "${board_line//[A-Z]/ }"; done < "${RESULT_FILE}"
fi fi
echo "Guess - $(( ROW+1 ))/6" echo "Guess - $(( ROW+1 ))/6"
@@ -83,12 +76,16 @@ function main() {
fi fi
done done
render_answer [[ "${BORDER}" == 1 ]] && echo -en "${CLEAR_DOWN}${INDENT}${INDENT}"
echo -en "${CLEAR_LINE}${BOLD}"
for (( i=0; i<WIDTH; i++ )); do
echo -en " ${ANSWER:$i:1} "
done
echo -en "${NEWLINE}${RESET}"
save_result
if [[ "${ONLINE}" == 1 && "${DAILY_DONE}" != 1 ]]; then if [[ "${ONLINE}" == 1 && "${DAILY_DONE}" != 1 ]]; then
echo "Share/view your game with -> curl \"${URL_BASE}/${RESULT_FILE}\"" echo "Share/view your game with -> curl \"${URL_BASE}/${RESULT_FILE}\""
echo "Share/view your stats with -> curl \"${URL_BASE}/stats/${USER}\""
echo "Check out the new homepage in your browser -> ${URL_BASE}"
while read -r board_line; do echo "${board_line//[A-Z]/ }"; done < "${RESULT_FILE}" while read -r board_line; do echo "${board_line//[A-Z]/ }"; done < "${RESULT_FILE}"
fi fi
exit 0 exit 0
@@ -140,15 +137,9 @@ function set_options() {
echo -en "${CLEAR_SCREEN}" echo -en "${CLEAR_SCREEN}"
declare -g USER declare -g USER
read -r -p "Username: " USER || exit 1 read -r -p "Username: " USER
USER="${USER,,}" USER="${USER,,}"
# Username is used in file names and URLs so only allow safe characters
while [[ -z "${USER}" || "${USER}" == *[!a-z0-9_]* || ${#USER} -gt 32 ]]; do
read -r -p "${CLEAR_UP1}Username (letters, numbers and _ only): " USER || exit 1
USER="${USER,,}"
done
declare -g DAY declare -g DAY
DAY=$(( EPOCHSECONDS / 86400 )) DAY=$(( EPOCHSECONDS / 86400 ))
@@ -182,15 +173,14 @@ function set_options() {
fi fi
fi fi
# Progress is saved after every guess so Ctrl-C only needs to tidy the terminal trap save_game SIGINT
trap 'echo -en "${SHOW_CURSOR}${RESET}"; exit 0' SIGINT
;; ;;
d) d)
DELAY=0 DELAY=0
;; ;;
h) h)
echo -e \ echo -e \
"Usage: wordle [OPTIONS] "Usage: <program_name> [OPTIONS]
Play a game for Wordle Play a game for Wordle
Options: Options:
@@ -199,7 +189,6 @@ Options:
-H Enable hard mode -H Enable hard mode
-S Enable strict hard mode (Cannot reuse grey letters) -S Enable strict hard mode (Cannot reuse grey letters)
-k disable on screen keyboard -k disable on screen keyboard
-o Enable online mode
-d Enable debug mode (no delay during letter reveal) -d Enable debug mode (no delay during letter reveal)
-h Show this help message and exit" -h Show this help message and exit"
@@ -238,54 +227,29 @@ function render_board() {
[[ "${BORDER}" == 1 ]] && echo -e "└─────────────────┘" [[ "${BORDER}" == 1 ]] && echo -e "└─────────────────┘"
} }
function render_answer() { function retrive_guess() {
[[ "${BORDER}" == 1 ]] && echo -en "${CLEAR_DOWN}${INDENT}${INDENT}"
echo -en "${CLEAR_LINE}${BOLD}"
for (( i=0; i<WIDTH; i++ )); do
echo -en " ${ANSWER:$i:1} "
done
echo -en "${NEWLINE}${RESET}"
}
# Exits non-zero when input has ended so the caller can quit
function retrieve_guess() {
if [[ -n $1 ]]; then if [[ -n $1 ]]; then
read -r -p "${CLEAR_UP1}$1: " GUESS || end_of_input read -r -p "${CLEAR_UP1}$1: " GUESS
else else
read -r -p "${CLEAR_2END}Enter Guess: " GUESS || end_of_input read -r -p "${CLEAR_2END}Enter Guess: " GUESS
fi fi
while [[ ${#GUESS} -ne 5 ]] || [[ ! "${GUESS,,}" =~ ^[a-z]{5}$ ]]; do while [[ ${#GUESS} -ne 5 ]] || [[ ! "${GUESS,,}" =~ ^[a-z]{5}$ ]]; do
read -r -p "${CLEAR_UP1}Must be 5 letters: " GUESS || end_of_input read -r -p "${CLEAR_UP1}Must be 5 letters: " GUESS
done done
declare valid_guess declare valid_guess
validate_guess "${GUESS}" ; valid_guess=$? validate_guess "${GUESS}" ; valid_guess=$?
declare message [[ ${valid_guess} == 1 ]] && GUESS=$(retrive_guess "Not A Valid Word")
declare -r pos="${HARD_MODE_POS}" [[ ${valid_guess} == 2 ]] && GUESS=$(retrive_guess "Not A Valid Guess")
declare -r letter="${GUESS:pos:1}" [[ ${valid_guess} == 3 ]] && GUESS=$(retrive_guess "GREEN Issue")
declare -ra ordinals=(1st 2nd 3rd 4th 5th) [[ ${valid_guess} == 4 ]] && GUESS=$(retrive_guess "YELLOW Issue")
[[ ${valid_guess} == 1 ]] && message="Not A Valid Word" [[ ${valid_guess} == 5 ]] && GUESS=$(retrive_guess "POOL Issue")
[[ ${valid_guess} == 3 ]] && message="${ordinals[pos]} letter must be ${greens[pos]}" [[ ${valid_guess} == 6 ]] && GUESS=$(retrive_guess "NEW Issue")
[[ ${valid_guess} == 4 ]] && message="Must contain ${yellows[pos]}"
[[ ${valid_guess} == 5 ]] && message="${letter^^} is not in the word"
[[ ${valid_guess} == 6 ]] && message="${ordinals[pos]} letter can't be ${letter^^}"
if [[ -n "${message}" ]]; then
GUESS=$(retrieve_guess "${message}") || exit 1
fi
echo -n "${GUESS^^}" echo -n "${GUESS^^}"
} }
# Called when read gets Ctrl-D. On a terminal treat it like Enter, but if
# input has really ended (closed pipe) exit, or the retry loop spins forever
function end_of_input() {
[[ -t 0 ]] || exit 1
# Ctrl-D doesn't move to a new line like Enter, which the prompts expect
echo -en "${NEWLINE}" >&2
}
function render_keyboard() { function render_keyboard() {
declare pos=$(( BORDER*2+7 )) declare pos=$(( BORDER*2+7 ))
echo -en "${RESET}\e[$pos;0H" echo -en "${RESET}\e[$pos;0H"
@@ -295,12 +259,7 @@ function render_keyboard() {
elif [[ $i == 19 ]]; then elif [[ $i == 19 ]]; then
echo -en "${NEWLINE} " echo -en "${NEWLINE} "
fi fi
declare key="${POOL:$i:1}" echo -n "${POOL:$i:1} "
if [[ -n "${KEYS[$key]}" ]]; then
echo -n "${KEYS[$key]}${BOLD}${LETTER}${key}${RESET} "
else
echo -n "${key} "
fi
done done
echo -en "${NEWLINE}" echo -en "${NEWLINE}"
} }
@@ -326,8 +285,6 @@ function hard_mode_check() {
declare GUESS=$1 declare GUESS=$1
for (( i=0; i<WIDTH; i++ )); do for (( i=0; i<WIDTH; i++ )); do
HARD_MODE_POS=$i # letter position that failed, for the error message
if [[ "${STRICT_HARD_MODE}" == 1 ]]; then if [[ "${STRICT_HARD_MODE}" == 1 ]]; then
[[ "${POOL}" == *${GUESS:i:1}* ]] || return 5 [[ "${POOL}" == *${GUESS:i:1}* ]] || return 5
fi fi
@@ -343,7 +300,7 @@ function hard_mode_check() {
for (( r=ROW-1; r>=0; r-- )); do for (( r=ROW-1; r>=0; r-- )); do
CHAR="${GUESS:i:1}" CHAR="${GUESS:i:1}"
PREV_CHAR="${YELLOWS[$r,$i]}" PREV_CHAR="${YELLOWS[$r,$i]}"
[[ "${CHAR}" == "${PREV_CHAR}" ]] && return 6 [[ "${CHAR}" == "${PREV_CHAR}" ]] && return 4
done done
done done
@@ -373,7 +330,6 @@ function check_guess() {
if [[ "${_char}" == "${_answer:$c:1}" ]]; then if [[ "${_char}" == "${_answer:$c:1}" ]]; then
greens[c]="${_char}" greens[c]="${_char}"
KEYS[$_char]="${GREEN}"
BOARD[$_row,$c]="${GREEN}${BOLD}${LETTER} ${_char} ${UNBOLD}" BOARD[$_row,$c]="${GREEN}${BOLD}${LETTER} ${_char} ${UNBOLD}"
continue continue
fi fi
@@ -382,7 +338,6 @@ function check_guess() {
chars="${chars/${_char}/}" chars="${chars/${_char}/}"
yellows[c]="${_char}" # volatile - stores previous row yellows yellows[c]="${_char}" # volatile - stores previous row yellows
YELLOWS[$_row,$c]="${_char}" # stores all yellow from all rounds YELLOWS[$_row,$c]="${_char}" # stores all yellow from all rounds
[[ "${KEYS[$_char]}" != "${GREEN}" ]] && KEYS[$_char]="${YELLOW}"
BOARD[$_row,$c]="${YELLOW}${BOLD}${LETTER} ${_char} ${UNBOLD}" BOARD[$_row,$c]="${YELLOW}${BOLD}${LETTER} ${_char} ${UNBOLD}"
fi fi
[[ "${ANSWER}" =~ ${_char} ]] && continue [[ "${ANSWER}" =~ ${_char} ]] && continue
@@ -395,6 +350,7 @@ function check_guess() {
} }
function reveal_row() { function reveal_row() {
REVEALING=1
echo -en "${UP1}${SAVE}" echo -en "${UP1}${SAVE}"
echo -en "${HIDE_CURSOR}" echo -en "${HIDE_CURSOR}"
# Must increment by 1 Row as 0 == 1 for escape code # Must increment by 1 Row as 0 == 1 for escape code
@@ -414,9 +370,8 @@ function load_game() {
source "${PROGRESS_FILE}" source "${PROGRESS_FILE}"
} }
# Takes the row the game should resume from # shellcheck disable=SC2317 # Don't warn about unreachable commands in this function
function save_game() { function save_game() {
declare -r row=$1
: > "${PROGRESS_FILE}" : > "${PROGRESS_FILE}"
for key in "${!BOARD[@]}"; do for key in "${!BOARD[@]}"; do
@@ -427,21 +382,8 @@ function save_game() {
echo "YELLOWS[$key]='${YELLOWS[$key]}'" >> "${PROGRESS_FILE}" echo "YELLOWS[$key]='${YELLOWS[$key]}'" >> "${PROGRESS_FILE}"
done done
for key in "${!KEYS[@]}"; do
echo "KEYS[$key]='${KEYS[$key]}'" >> "${PROGRESS_FILE}"
done
# Previous row's hints, needed by hard_mode_check after a resume
for key in "${!greens[@]}"; do
echo "greens[$key]='${greens[$key]}'" >> "${PROGRESS_FILE}"
done
for key in "${!yellows[@]}"; do
echo "yellows[$key]='${yellows[$key]}'" >> "${PROGRESS_FILE}"
done
{ {
echo "ROW=${row}" echo "ROW=$(( ROW + REVEALING ))"
echo "POOL=${POOL}" echo "POOL=${POOL}"
echo "HARD_MODE=${HARD_MODE}" echo "HARD_MODE=${HARD_MODE}"
echo "STRICT_HARD_MODE=${STRICT_HARD_MODE}" echo "STRICT_HARD_MODE=${STRICT_HARD_MODE}"
@@ -450,17 +392,26 @@ function save_game() {
echo "IN_PROGRESS=1" echo "IN_PROGRESS=1"
[[ "${PLAYED}" == 1 ]] && echo 'PLAYED=1' [[ "${PLAYED}" == 1 ]] && echo 'PLAYED=1'
} >> "${PROGRESS_FILE}" } >> "${PROGRESS_FILE}"
echo -en "${RESET}"
[[ -z $1 ]] && exit 0
} }
function save_result() { function save_result() {
[[ "${ONLINE}" == 0 ]] && return 0 [[ "${ONLINE}" == 0 ]] && return 0
[[ "${ONLINE}" == 1 ]] && update_stats
if [[ "${ONLINE}" == 1 ]] && [[ "${DAILY_DONE}" != 1 ]]; then if [[ "${ONLINE}" == 1 ]] && [[ "${DAILY_DONE}" != 1 ]]; then
update_stats
render_board > "${RESULT_FILE}" render_board > "${RESULT_FILE}"
render_board > "${CUR_RESULT_FILE}" render_board > "${CUR_RESULT_FILE}"
if [[ $WIN != 5 ]]; then if [[ $WIN != 5 ]]; then
render_answer >> "${RESULT_FILE}" [[ "${BORDER}" == 1 ]] && echo -en "${CLEAR_DOWN}${INDENT}${INDENT}" >> "${RESULT_FILE}"
render_answer >> "${CUR_RESULT_FILE}" echo -en "${CLEAR_LINE}${BOLD}" >> "${RESULT_FILE}"
for (( i=0; i<WIDTH; i++ )); do
echo -en " ${ANSWER:$i:1} " >> "${RESULT_FILE}"
echo -en " ${ANSWER:$i:1} " >> "${CUR_RESULT_FILE}"
done
echo -en "${NEWLINE}${RESET}" >> "${RESULT_FILE}"
echo -en "${NEWLINE}${RESET}" >> "${CUR_RESULT_FILE}"
fi fi
fi fi
echo 'PLAYED=1' > "${PROGRESS_FILE}" echo 'PLAYED=1' > "${PROGRESS_FILE}"
@@ -500,7 +451,6 @@ function update_stats() {
integer_part=$(( result / 100 )) integer_part=$(( result / 100 ))
decimal_part=$(( result % 100 )) decimal_part=$(( result % 100 ))
[[ ${decimal_part} -lt 10 ]] && decimal_part="0${decimal_part}"
AVERAGE="${integer_part}.${decimal_part}" AVERAGE="${integer_part}.${decimal_part}"
fi fi