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 |
@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
done
IMHO, 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
done
Here'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!
@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.