How to use:
./wordle.sh
Or try the unlimit mode:
./wordle.sh unlimit
| words=($(grep '^\w\w\w\w\w$' /usr/share/dict/words | tr '[a-z]' '[A-Z]')) | |
| actual=${words[$[$RANDOM % ${#words[@]}]]} end=false guess_count=0 max_guess=6 | |
| if [[ $1 == "unlimit" ]]; then | |
| max_guess=999999 | |
| fi | |
| while [[ $end != true ]]; do | |
| guess_count=$(( $guess_count + 1 )) | |
| if [[ $guess_count -le $max_guess ]]; then | |
| echo "Enter your guess ($guess_count / $max_guess):" | |
| read guess | |
| guess=$(echo $guess | tr '[a-z]' '[A-Z]') | |
| if [[ " ${words[*]} " =~ " $guess " ]]; then | |
| output="" remaining="" | |
| if [[ $actual == $guess ]]; then | |
| echo "You guessed right!" | |
| for ((i = 0; i < ${#actual}; i++)); do | |
| output+="\033[30;102m ${guess:$i:1} \033[0m" | |
| done | |
| printf "$output\n" | |
| end=true | |
| else | |
| for ((i = 0; i < ${#actual}; i++)); do | |
| if [[ "${actual:$i:1}" != "${guess:$i:1}" ]]; then | |
| remaining+=${actual:$i:1} | |
| fi | |
| done | |
| for ((i = 0; i < ${#actual}; i++)); do | |
| if [[ "${actual:$i:1}" != "${guess:$i:1}" ]]; then | |
| if [[ "$remaining" == *"${guess:$i:1}"* ]]; then | |
| output+="\033[30;103m ${guess:$i:1} \033[0m" | |
| remaining=${remaining/"${guess:$i:1}"/} | |
| else | |
| output+="\033[30;107m ${guess:$i:1} \033[0m" | |
| fi | |
| else | |
| output+="\033[30;102m ${guess:$i:1} \033[0m" | |
| fi | |
| done | |
| printf "$output\n" | |
| fi | |
| else | |
| echo "Please enter a valid word with 5 letters!"; | |
| guess_count=$(( $guess_count - 1 )) | |
| fi | |
| else | |
| echo "You lose! The word is:" | |
| echo $actual | |
| end=true | |
| fi | |
| done | 
Great stuff! I made a version with variable # of letters and multiple languages at https://github.com/pforret/shwordle
@rawiriblundell
Thank you very much for pointing this out. I had seen your earlier post, and had made a mental note to study that later.  I like newer practices and cleaner code.  But I had not realized you had shrunk it down to 26 at that time.
BTW, if we do not care much about code readability, I think the code can be in one line by stringing all together, no? :) I do not think there is any construct that requires a line end. I am not shell expert. So the popular notion of lines of code is rather fluid.
@tb3088
Thanks for posting.  I do not think I had seen this particular formatting and syntax of bash before.  I like this.  Has this particular style got an accepted name such as Posix?  You have used the no-op : extensively. I am intrigued and like to study further.
I would call it a 35 line version, really. There are 6 blank lines :)
PS. By experimenting I found out ${a:=3} is really defining a as a constant and setting it! Cannot be reassigned a new value before unsetting it. Great. I am eager to find out the name and the full set of syntax.
@CqN I'm not doing anything exotic. It's how I write all shell code. take a gander at https://github.com/tb3088/shell-environment/blob/master/.functions
Also read up on Bash Parameter Expansion. The original post was written rather poorly (disorganized) and tried to keep using strings when arrays make it so much more straightforward.
I found that my dict/words contains accented words such as "éCLAT, éPéES, éTUDE". \w matches accented e, but [a-z] would not.
@tb3088
Mathew, thanks for the feedback.
Now how would you perform your bash magic on this line
read -ep "Enter your guess ($LETTERS letters | $j/$ROUNDS): " guess || exit
so, I will see the guess I type in shown in capital?
Here's a variant done in (Edit:) under 20 lines of bash: https://gist.github.com/aaronNGi/04a209b1cd17f1fa03af7a87eb14cc42
Cleaned up a few things. Biggest changes:
read(1) and to save ourselves an if indentation on the rest of the logic with a while.#!/bin/bash
function wordsgrep() {
  (IFS=$'\n'; echo "${words[*]}") | grep -qixF "${1:-inv.alid}"
}
words=($(grep -xE '\w{5}' /usr/share/dict/words | tr '[:lower:]' '[:upper:]'))
actual=${words[$[$RANDOM % ${#words[@]}]]} guess_count=0 max_guess=6
[[ "${1//unlimit}" != "${1:-}" ]] && max_guess=999999
while true; do
    guess_count=$(( $guess_count + 1 ))
    if [[ $guess_count -le $max_guess ]]; then
        while read -r -p "Enter your guess ($guess_count / $max_guess): " guess; do
            wordsgrep "$guess" && break
            [[ ${#guess} != 5 ]] && echo "Too short/long." && continue
            echo "Not a real word."
        done
        guess="$(tr '[:lower:]' '[:upper:]' <<<"$guess")"
        output="" remaining=""
        if [[ $actual == $guess ]]; then
            echo "You guessed right!"
            for ((i = 0; i < ${#actual}; i++)); do
                output+="$(tput setaf 0)$(tput setab 10) ${guess:$i:1} $(tput sgr0)"
            done
            echo "$output"
            break
        else
            for ((i = 0; i < ${#actual}; i++)); do
                if [[ "${actual:$i:1}" != "${guess:$i:1}" ]]; then
                    remaining+=${actual:$i:1}
                fi
            done
            for ((i = 0; i < ${#actual}; i++)); do
                if [[ "${actual:$i:1}" != "${guess:$i:1}" ]]; then
                    if [[ "$remaining" == *"${guess:$i:1}"* ]]; then
                        output+="$(tput setaf 0)$(tput setab 11) ${guess:$i:1} $(tput sgr0)"
                        remaining=${remaining/"${guess:$i:1}"/}
                    else
                        output+="$(tput setaf 0)$(tput setab 15) ${guess:$i:1} $(tput sgr0)"
                    fi
                else
                    output+="$(tput setaf 0)$(tput setab 10) ${guess:$i:1} $(tput sgr0)"
                fi
            done
            echo "$output"
        fi
    else
        echo "You lose! The word is:"
        echo $actual
        break
    fi
done--- a/wordle.sh	2022-02-09 15:45:06.000000000 -0800
+++ b/wordle.sh	2022-02-09 15:45:06.000000000 -0800
@@ -1,23 +1,29 @@
-words=($(grep '^\w\w\w\w\w$' /usr/share/dict/words | tr '[a-z]' '[A-Z]'))
-actual=${words[$[$RANDOM % ${#words[@]}]]} end=false guess_count=0 max_guess=6
-if [[ $1 == "unlimit" ]]; then
-    max_guess=999999
-fi
-while [[ $end != true ]]; do
+#!/bin/bash
+
+function wordsgrep() {
+  (IFS=$'\n'; echo "${words[*]}") | grep -qixF "${1:-inv.alid}"
+}
+
+words=($(grep -xE '\w{5}' /usr/share/dict/words | tr '[:lower:]' '[:upper:]'))
+actual=${words[$[$RANDOM % ${#words[@]}]]} guess_count=0 max_guess=6
+[[ "${1//unlimit}" != "${1:-}" ]] && max_guess=999999
+while true; do
     guess_count=$(( $guess_count + 1 ))
     if [[ $guess_count -le $max_guess ]]; then
-        echo "Enter your guess ($guess_count / $max_guess):"
-        read guess
-        guess=$(echo $guess | tr '[a-z]' '[A-Z]')
-        if [[ " ${words[*]} " =~ " $guess " ]]; then
+        while read -r -p "Enter your guess ($guess_count / $max_guess): " guess; do
+            wordsgrep "$guess" && break
+            [[ ${#guess} != 5 ]] && echo "Too short/long." && continue
+            echo "Not a real word."
+        done
+        guess="$(tr '[:lower:]' '[:upper:]' <<<"$guess")"
             output="" remaining=""
             if [[ $actual == $guess ]]; then
                 echo "You guessed right!"
                 for ((i = 0; i < ${#actual}; i++)); do
-                    output+="\033[30;102m ${guess:$i:1} \033[0m"
+                output+="$(tput setaf 0)$(tput setab 10) ${guess:$i:1} $(tput sgr0)"
                 done
-                printf "$output\n"
-                end=true
+            echo "$output"
+            break
             else
                 for ((i = 0; i < ${#actual}; i++)); do
                     if [[ "${actual:$i:1}" != "${guess:$i:1}" ]]; then
@@ -27,24 +33,20 @@
                 for ((i = 0; i < ${#actual}; i++)); do
                     if [[ "${actual:$i:1}" != "${guess:$i:1}" ]]; then
                         if [[ "$remaining" == *"${guess:$i:1}"* ]]; then
-                            output+="\033[30;103m ${guess:$i:1} \033[0m"
+                        output+="$(tput setaf 0)$(tput setab 11) ${guess:$i:1} $(tput sgr0)"
                             remaining=${remaining/"${guess:$i:1}"/}
                         else
-                            output+="\033[30;107m ${guess:$i:1} \033[0m"
+                        output+="$(tput setaf 0)$(tput setab 15) ${guess:$i:1} $(tput sgr0)"
                         fi
                     else
-                        output+="\033[30;102m ${guess:$i:1} \033[0m"
+                    output+="$(tput setaf 0)$(tput setab 10) ${guess:$i:1} $(tput sgr0)"
                     fi
                 done
-                printf "$output\n"
-            fi
-        else
-            echo "Please enter a valid word with 5 letters!";
-            guess_count=$(( $guess_count - 1 ))
+            echo "$output"
         fi
     else
         echo "You lose! The word is:"
         echo $actual
-        end=true
+        break
     fi
 doneIMHO, storing the words as an array, while a neat trick, also gets pretty slow pretty quickly. A version without arrays to follow....
As promised, the version without arrays. Also makes some minor stylistic changes, deletes an unnecessary loop, and adds an "abandon" feature (press CTRL-D at the prompt). 36 lines, total.
actual="$(sort -R /usr/share/dict/words | grep -xEm 1 '\w{5}' | tr '[:lower:]' '[:upper:]')"
guess_count=0 max_guess=6
[[ "${1//unlimit}" != "${1:-}" ]] && max_guess=999999
while true; do
    guess_count=$(( guess_count + 1 ))
    if [[ $guess_count -le $max_guess ]]; then
        while read -r -p "Enter your guess ($guess_count / $max_guess): " guess; do
            grep -ixF "${guess:-inv.alid}" /usr/share/dict/words | grep -xqE '\w{5}' && break
            [[ ${#guess} != 5 ]] && echo "Too short/long." && continue
            echo "Not a real word."
        done
        [ ${#guess} -eq 0 ] && echo && echo "Giving up so soon?  The answer was $actual." && break
        guess="$(tr '[:lower:]' '[:upper:]' <<<"$guess")"
        output="" remaining=""
        for ((i = 0; i < ${#actual}; i++)); do
            [[ "${actual:$i:1}" != "${guess:$i:1}" ]] && remaining+=${actual:$i:1}
        done
        for ((i = 0; i < ${#actual}; i++)); do
            if [[ "${actual:$i:1}" != "${guess:$i:1}" ]]; then
                if [[ "$remaining" == *"${guess:$i:1}"* ]]; then
                    output+="$(tput setaf 0)$(tput setab 11) ${guess:$i:1} $(tput sgr0)"
                    remaining=${remaining/"${guess:$i:1}"/}
                else
                    output+="$(tput setaf 0)$(tput setab 15) ${guess:$i:1} $(tput sgr0)"
                fi
            else
                output+="$(tput setaf 0)$(tput setab 10) ${guess:$i:1} $(tput sgr0)"
            fi
        done
        echo "$output"
        [ "$actual" = "$guess" ] && echo "You guessed right!" && break
    else
        echo "You lose!  The word was $(tput setaf 1)$(tput bold)$actual$(tput sgr0)."
        break
    fi
doneHere's a challenge for someone: Can we make a game that uses more than one language at a time? Would that just involve using two dictionaries? Or would the game need different rules?
I wrote a SAS version and placed it here sascommunities/wordle-sas. Uses the word lists from cfreshman (thanks) and arrays to check guesses.
I learned about this from an Oreilly blog post. Good job!
40 lines, and doesn't use any memory at all compared to implementations above. Also much easier to follow along IMO. Assumes suitable dictionary exists. So who's going to be the first to write it in 1 line of obfuscated Perl? This game is just a word version of Master Mind(tm)