Search code examples
terminalzshascii-art

Bash/Zsh - How to randomly color letters in ascii art block text?


I am writing a zsh script to display ascii art in different colors.

Basically I have ascii text written in block letters and I want to write a script that randomizes the colors of each one using color escaped characters.

The text is

 [like this](https://pastebin.com/nCs6kNzu) 

1$$$$$$$\ 2 $$$$$$$$\ 3$$\   $$\ 4 $$$$$$\  5$$\       6$$$$$$$\  
1$$  __$$\2 $$  _____|3$$ |  $$ |4$$  __$$\ 5$$ |      6$$  __$$\ 
1$$ |  $$ 2|$$ |      3$$ |  $$ |4$$ /  $$ |5$$ |      6$$ |  $$ |
1$$$$$$$\ 2|$$$$$\    3$$$$$$$$ |4$$ |  $$ |5$$ |      6$$ |  $$ |
1$$  __$$\2 $$  __|   3$$  __$$ |4$$ |  $$ |5$$ |      6$$ |  $$ |
1$$ |  $$ 2|$$ |      3$$ |  $$ |4$$ |  $$ |5$$ |      6$$ |  $$ |
1$$$$$$$  2|$$$$$$$$\ 3$$ |  $$ |4 $$$$$$  |5$$$$$$$$\ 6$$$$$$$  |
1\_______/2 \________|3\__|  \__|4 \______/ 5\________|6\_______/ 

I want to make a script that:

  1. Creates an array of six random colors

  2. Randomizes the order of the colors

  3. You'll notice in the ascii there are number characters before each letter e.g. 1 is before 'B', 2 is before 'E'. I want to replace each one with a color so that each 'letter' of the ascii art is a different color.

  4. This is my attempt on the code so far and it's not working:


# Define array of colors
colors=("[31m" "[32m" "[33m" "[34m" "[35m" "[36m")

# Randomize the order of colors in the array
colors=($(printf "%s\n" "${colors[@]}" | shuf))

# Read ASCII art from file or source
ascii_art=$(<ascii_art.txt)

# Function to replace number characters with colors
replace_numbers_with_colors() {
    local text="$1"
    local result=""
    local index=0
    for ((i = 0; i < ${#text}; i++)); do
        local char="${text:i:1}"
        if [[ $char =~ [0-9] ]]; then
            result="${result}${colors[index]}$char[0m"
            ((index++))
        else
            result="${result}$char"
        fi
    done
    echo "$result"
}

# Replace numbers with colors in ASCII art
colored_ascii_art=$(replace_numbers_with_colors "$ascii_art")

# Print the colored ASCII art
echo -e "$colored_ascii_art"

So far it only colors one letter and doesn't delete the numbers.

The output looks like this

and only the first letter of the first line is colored.

Please let me know how to approach this.

I also thought keeping the text in six different elements of an array, then I could iterate through it two dimensionally; by array element and by line number


Solution

  • This can be done as a set of text substitutions: for each part of an ascii-art letter, change a number followed by some text to color codes followed by that text (and maybe some closing color codes).

    With that in mind, we can use a zsh text replacement expansion. Try this:

    #!/usr/bin/env zsh
    
    colorByNumbers() {
     local c=(red green blue cyan magenta yellow black white)
     c=( /(e['reply=("$c[@]")']noe['REPLY=$RANDOM']) )
     print -Pr -- \
     ${(*)1//(#b)([[:digit:]])([^[:digit:]]##)/%F{${c[match[1]]}}${match[2]//\%/%%}%f}
    }
    
    
    text='1$$$$$$$\ 2 $$$$$$$$\ 3$$\   $$\ 4 $$$$$$\  5$$\       6$$$$$$$\  
    1$$  __$$\2 $$  _____|3$$ |  $$ |4$$  __$$\ 5$$ |      6$$  __$$\ 
    1$$ |  $$ 2|$$ |      3$$ |  $$ |4$$ /  $$ |5$$ |      6$$ |  $$ |
    1$$$$$$$\ 2|$$$$$\    3$$$$$$$$ |4$$ |  $$ |5$$ |      6$$ |  $$ |
    1$$  __$$\2 $$  __|   3$$  __$$ |4$$ |  $$ |5$$ |      6$$ |  $$ |
    1$$ |  $$ 2|$$ |      3$$ |  $$ |4$$ |  $$ |5$$ |      6$$ |  $$ |
    1$$$$$$$  2|$$$$$$$$\ 3$$ |  $$ |4 $$$$$$  |5$$$$$$$$\ 6$$$$$$$  |
    1\_______/2 \________|3\__|  \__|4 \______/ 5\________|6\_______/ '
    
    colorByNumbers $text
    

    Some references:

    • local c=(...) - array that maps numbers to color specifiers. Note that array indexes in zsh start with 1.
    • c=( ... $RANDOM ...) - randomly shuffles the color array. Based on this answer.
    • ${...//.../...} - parameter expansion. With //, all instances of the input pattern will be replaced with the output pattern.
    • ${(*)...} - parameter expansion flag that enables extended globbing patterns.
    • (#b) - turns on backreferences.
    • ([[:digit:]])([^[:digit:]]##) - input pattern.
      • [[:digit:]] - a single numeric digit.
      • [^[:digit:]]## - one or more characters that are not digits.
      • (...)(...) - groupings used used for backreferences.
    • %F{${c[match[1]]}}${match[2]}%f - output pattern.
      • %F and %f are zsh prompt sequences. %F{...} changes the color to the value in the braces, and %f returns the color to its prior setting.
      • ${c[match[1]]} - this will be replaced with a value from the c array of colors. It uses the first group from the input pattern, i.e. the single digit.
      • ${match[2]//\%/%%} - the second group from the input pattern; it's a piece of the text used to form the ascii-art letters. //\%/%% will escape any % characters so they are not interpreted as prompt substitutions.
      • The net result will be something like: %F{cyan}$$ | $$ |%f.
    • print -P - prints the argument to stdout. With -P, the %F and %f prompt sequences will be converted to escape codes for the terminal.