Matrix Reimagined: Crafting Digital Rain with Bash and ChatGPT

Just for fun, and I have no idea why I thought about it, I decided to work with ChatGPT (4) to build a simple bash-based version of the Matrix Digital Rain. I know there’s already better versions, like cmatrix, but we do not do things because they are easy. We do them because we are bored.

I’ve asked ChatGPT to heavily comment the code for us so that we can see exactly what’s going on:

#!/bin/bash

# This script creates a Matrix-style falling text effect in the terminal.

# Define strings for extra characters (Japanese Katakana) and extended ASCII characters
extra_chars="カキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン"
extended_ascii="│┤┐└┴┬├─┼┘┌≡"

# Define arrays of color codes for a fading green color effect, and a static color
fade_colors=('\033[38;2;0;255;0m' '\033[38;2;0;192;0m' '\033[38;2;0;128;0m' '\033[38;2;0;64;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;16;0m' '\033[38;2;0;8;0m') # Fading green colors
static_color='\033[38;2;0;0;0m' # Static dark green color
white_bold='\033[1;37m' # White and bold for the primary character

# Get terminal dimensions
COLUMNS=$(tput cols) # Number of columns in the terminal
ROWS=$(tput lines) # Number of rows in the terminal


# Hide the cursor for a cleaner effect and clear the screen
echo -ne '\033[?25l'
clear

# Function to generate a random character from the set of extra characters and extended ASCII
random_char() {
	local chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789${extra_chars}${extended_ascii}"
	echo -n "${chars:RANDOM%${#chars}:1}"
}

# Generate a list of 1000 random characters
random_chars=""
for (( i=0; i<1000; i++ )); do
	random_chars+=$(random_char) # Add a random character to the end of the string
done

# Initialize a counter for cycling through the random characters
char_counter=0 # Counter for cycling through the random characters

# Initialize arrays to keep track of the position and trail characters of each column
positions=() # Array to store the current position in each column
trail_chars=() # Array to store the trail characters in each column
for (( c=1; c<=COLUMNS; c++ )); do
	positions[$c]=$((RANDOM % ROWS)) # Random starting position for each column
	trail_chars[$c]="" # Start with an empty trail for each column
done

# Function to update the display with the falling text effect
update_line() {
	local last_pos=0  # Track the last position to optimize cursor movement

	for (( c=1; c<=COLUMNS; c++ )); do
		# Randomly skip updating some columns to create a dynamic effect
		if [ $((RANDOM % 4)) -ne 0 ]; then
			continue
		fi

		local new_char=${random_chars:$char_counter:1} # Select the next character from the random string
		char_counter=$(( (char_counter + 1) % 1000 )) # Update the counter, cycling back after 1000

		local pos=${positions[$c]} # Current position in this column
		local trail=${trail_chars[$c]} # Current trail of characters in this column

		trail_chars[$c]="${new_char}${trail:0:$((ROWS - 1))}" # Update the trail by adding new character at the top

		# Render the trail of characters
		for (( i=0; i<${#trail}; i++ )); do
			local trail_pos=$((pos - i)) # Calculate the position for each character in the trail
			if [ $trail_pos -ge 0 ] && [ $trail_pos -lt $ROWS ]; then
				local color=${fade_colors[i]:-$static_color} # Choose color from the fade array or static color if beyond the array
				if [ $i -eq 0 ]; then
					color=$white_bold # First character in the trail is white and bold
				fi
				if [ $last_pos -ne $trail_pos ]; then
					printf "%b" "\033[${trail_pos};${c}H" # Move cursor to the right position
					last_pos=$trail_pos
				fi
				printf "%b" "${color}${trail:$i:1}\033[0m" # Print the character with color
			fi
		done

		positions[$c]=$((pos + 1)) # Update the position for the next cycle
		if [ $pos -ge $((ROWS + ${#fade_colors[@]})) ]; then
			positions[$c]=0 # Reset position if it moves off screen
			trail_chars[$c]=""
		fi
	done
}

# Main loop for continuous execution of the update_line function
while true; do
	update_line
done

# Reset terminal settings on exit (show cursor, clear screen, reset text format)
echo -ne '\033[?25h' # Show cursor
clear
tput sgr0 # Reset text format
Code language: PHP (php)

Challenges Faced

Developing the Matrix Digital Rain script presented specific challenges, especially in terms of performance. The initial use of tput for cursor manipulation proved inefficient for the dynamic text display. This issue was resolved by switching to printf and ANSI escape sequences, which significantly enhanced the rendering performance.

Another problem arose with the use of fullwidth Katakana characters, which were incompatible with monospaced fonts, disrupting the visual flow. The solution involved adopting halfwidth Katakana, ensuring better compatibility and preserving the uniformity essential for the Matrix-style effect.

Another notable challenge emerged in development: the inefficiency of generating random characters on the fly. Due to Bash’s slower handling of string functions, this method significantly hindered performance. To tackle this, a strategic shift was made from real-time character generation to utilizing a predefined lookup table.

This approach involved generating a large set of pseudorandom characters before entering the main loop of the program. By doing so, I could rapidly access this table during runtime, boosting performance. This change played a crucial role in maintaining fluidity and responsiveness. It also preserved the illusion of randomness, essential for the authentic Matrix effect, thus striking a balance between performance efficiency and visual fidelity.

If you’d like to see what it looks like, here’s an example:

So yeah. That’s it I guess? Enjoy!

Other Posts Not Worth Reading

Hey, You!

Like this kind of garbage? Subscribe for more! I post like once a month or so, unless I found something interesting to write about.