Mostrando las entradas con la etiqueta animacion bash. Mostrar todas las entradas
Mostrando las entradas con la etiqueta animacion bash. Mostrar todas las entradas

Arquitectura del movimiento y la persistencia visual: el Pacman y la máscara binaria.


Arquitectura del movimiento y la persistencia visual: el Pacman y la máscara binaria.

Análisis del script de presentación ASCII de los videos de Entropía binaria para 2026.

En la programación de animaciones en terminal, mover un objeto de un punto A a un punto B es trivial; lo verdaderamente complejo surge cuando ese objeto debe interactuar con el entorno, modificándolo (permanentemente) a su paso. En el caso del script que estamos estudiando, el desafío es doble: el personaje debe recorrer la pantalla en un patrón de zigzag preciso y, al mismo tiempo, "ir comiéndose" al planeta para revelar un logotipo oculto debajo. Para lograr esto, no podemos pensar en el movimiento y en el dibujo como procesos separados; ambos dependen de un mapa de memoria compartido.

Para poder comprender el movimiento, primero debemos observar cómo se gestionan los límites. El Pacman no se mueve libremente; está confinado a un bucle lógico. En cada ciclo del procesador, su coordenada horizontal varía según una variable de dirección. Sin embargo, para lograr el efecto de escritura en zigzag, el script evalúa constantemente si el personaje ha superado los márgenes predefinidos a la izquierda o a la derecha del planeta. Cuando esto ocurre, suceden tres cosas simultáneamente: la dirección se invierte (se multiplica por -1), la coordenada vertical disminuye para subir un renglón, y el personaje se reposiciona en el borde opuesto. Este comportamiento cíclico asegura que el personaje barra toda la superficie del objeto central sin necesidad de programar cada giro manualmente.

Pero la magia real del script reside en cómo "borra" el planeta sin borrar el logo. Aquí es en donde entra en juego el concepto de matriz asociativa "array asociativo". En BASH, una matriz (array) común, se indiza (indexa) con números (0, 1, 2,...), pero un array asociativo nos permite usar cadenas de texto como índices. El script declara una variable llamada visitados. Cada vez que el Pacman se mueve, no solo cambia su posición en pantalla, sino que calcula qué coordenadas del planeta está tocando en ese momento. Luego, registra esas coordenadas exactas en el array asociativo usando el formato "fila,columna" como clave y asignándole un valor de "1". Es como si el Pacman fuera dejando un rastro de migajas invisibles en la memoria del ordenador, marcando "aquí estuve".

El paso final ocurre en el motor de renderizado. Cuando el script se dispone a dibujar el planeta línea por línea, no imprime ciegamente los caracteres del planeta. En su lugar, para cada celda de la cuadrícula, consulta el array visitados (de ahí su nombre). El programa se hace una pregunta simple: "¿ha pasado el Pacman por la coordenada Y,X actual?". Si la respuesta es no (el valor no existe o es 0), el script dibuja el caracter correspondiente al planeta (tierra o agua). Pero si la respuesta es sí (el valor es 1), el script ignora el planeta y busca en su lugar qué caracter corresponde a esa misma posición en el sprite del logotipo de Entropía binaria. De esta forma, no estamos realmente "borrando" nada; estamos alternando dinámicamente entre dos capas de imágenes (la capa del planeta y la capa del logo) utilizando la posición histórica del Pacman como una máscara de recorte en tiempo real.

Disección del código: implementación del motor de movimiento y revelado.

A continuación, he extraído y aislado la lógica exacta que controla el movimiento en zigzag, la detección de colisión (máscara) y el renderizado condicional.

Nota: para que este código sea funcional de manera independiente sin necesidad de descargar los archivos de texto externos (arte_ascii/...), he sustituido la función de carga de archivos por la definición manual de dos sprites simples (un cuadrado de "Planeta" y un cuadrado de "Logo") y un Pacman básico. El resto de la lógica (movimiento, array visitados, actualizar_mascara y el bucle de dibujo) es textualmente la del script original.


#!/bin/bash

c_borde_planeta="\e[34m"
c_tierra_planeta="\e[33m"
c_agua_planeta="\e[36m"
c_pacman="\e[1;33m"
c_texto="\e[1;37m"

# Colores logo.
c_logo_1_relleno="\e[38;5;52m"  # Marrón oscuro.
c_logo_1_fondo="\e[38;5;220m"   # Dorado.

# Ajustes de posición planeta y logo.
ajuste_planeta_y=0; ajuste_planeta_x=0
ajuste_logo_y=0; ajuste_logo_x=0

limite_pacman_izq=19
limite_pacman_der=5

vel_movimiento=50000000         # Velocidad pacman comiendo.
vel_anim=150000000              # Animación boca (150ms).

dir_arte="arte_ascii"

if [[ ! -d "$dir_arte" ]];then echo "Error: Falta '$dir_arte'";exit 1;fi

tput civis;tput rmam;clear      # rmam = desactivar auto-margen.
trap "tput cnorm;tput smam;echo -e '\e[0m';clear;exit" SIGINT 
c_reset="\e[0m"

# Estado inicial.
estado_juego=0        
filas_visitadas=0     
set_color_logo=1      
texto_pac=""          

declare -A visitados

# CARGA DE SPRITES DESDE ARCHIVOS DE TEXTO PLANO.
cargar_sprite() {
    local archivo="$dir_arte/$1"
    local -n ref_arr=$2; local -n ref_ancho=$3; local -n ref_alto=$4
    
    if [[ ! -f "$archivo" ]];then echo "Error: falta '$archivo'";tput cnorm;exit 1;fi
    
    mapfile -t ref_arr < "$archivo"
    ref_alto=${#ref_arr[@]}
    ref_ancho=0
    
    for linea in "${ref_arr[@]}";do
        len=${#linea}
        if [[ $len -gt $ref_ancho ]];then ref_ancho=$len;fi
    done
}

declare -a arte_planeta arte_logo pac_cerrado pac_abierto_izq pac_abierto_der 
cargar_sprite "planeta"             arte_planeta     ancho_planeta   alto_planeta
cargar_sprite "logo_entropía"       arte_logo        ancho_logo      alto_logo
cargar_sprite "pacman_cerrado"      pac_cerrado      ancho_pac       alto_pac
cargar_sprite "pacman_abierto_izq"  pac_abierto_izq  tmp_w           tmp_h
cargar_sprite "pacman_abierto_der"  pac_abierto_der  tmp_w           tmp_h

# POSICIONAMIENTO INICIAL.
ancho_term=$(tput cols);alto_term=$(tput lines)

offset_x=$(( ((ancho_term - ancho_planeta) / 2) + ajuste_planeta_x ))
offset_y=$(( ((alto_term - alto_planeta) / 2) + ajuste_planeta_y ))

logo_rel_x=$(( ((ancho_planeta - ancho_logo) / 2) + ajuste_logo_x ))
logo_rel_y=$(( ((alto_planeta - alto_logo) / 2) + ajuste_logo_y ))

pac_x=$(( offset_x + ancho_planeta + 2 ))
pac_y=$(( offset_y + alto_planeta - alto_pac )) 
pac_dir=-1;estado_pac=0;idx_texto=0

# LÓGICA DE MÁSCARA (EL BORRADO)
actualizar_mascara() {
    local -n ref_sprite_actual
    if [[ $estado_pac -eq 0 ]];then ref_sprite_actual=pac_cerrado
    elif [[ $pac_dir -eq 1 ]];then ref_sprite_actual=pac_abierto_der
    else ref_sprite_actual=pac_abierto_izq
    fi

    local rel_pac_x=$((pac_x - offset_x))
    local rel_pac_y=$((pac_y - offset_y))

    for ((py=0; py<alto_pac; py++));do
        local linea_sprite="${ref_sprite_actual[py]}"
        for ((px=0; px<ancho_pac; px++));do
            local char_en_sprite="${linea_sprite:px:1}"
            if [[ "$char_en_sprite" != " " ]] && [[ -n "$char_en_sprite" ]];then
                local ly=$((rel_pac_y + py))
                local lx=$((rel_pac_x + px))
                
                if [[ $ly -ge 0 ]] && [[ $ly -lt $alto_planeta ]] && [[ $lx -ge 0 ]] && [[ $lx -lt $ancho_planeta ]];then
                    visitados["$ly,$lx"]=1
                fi
            fi
        done
    done
}

# MOTOR GRÁFICO (RENDERIZADO).
dibujar_escena() {
    buffer="\e[H\e[2J" 

    # PLANETA / LOGO Eb.
    local c_relleno=$c_logo_1_relleno
    local c_fondo=$c_logo_1_fondo

    for ((i=0;i<alto_planeta;i++));do
        local pantalla_y=$((offset_y + i))
        buffer+="\e[${pantalla_y};${offset_x}H"
        
        local linea_planeta="${arte_planeta[i]}"
        local idx_fila_logo=$((i - logo_rel_y))
        local linea_logo=""
        if [[ $idx_fila_logo -ge 0 ]] && [[ $idx_fila_logo -lt $alto_logo ]];then linea_logo="${arte_logo[$idx_fila_logo]}";fi

        local linea_construida=""
        for ((j=0; j<ancho_planeta; j++)); do
            # LÓGICA DE VISIBILIDAD.
            local mostrar_logo=0
            if [[ "${visitados["$i,$j"]}" == "1" ]];then mostrar_logo=1;fi
            
            if [[ $mostrar_logo -eq 1 ]];then
                # MODO "LOGO".
                local idx_col_logo=$((j - logo_rel_x))
                if [[ $idx_fila_logo -ge 0 ]] && [[ $idx_fila_logo -lt $alto_logo ]] && [[ $idx_col_logo -ge 0 ]] && [[ $idx_col_logo -lt $ancho_logo ]];then
                    local char="${linea_logo:idx_col_logo:1}"
                    
                    if [[ "$char" == "#" ]];then linea_construida+="${c_fondo}█${c_reset}"
                    elif [[ "$char" == " " ]] || [[ -z "$char" ]];then linea_construida+=" "
                    else linea_construida+="${c_relleno}█${c_reset}"
                    fi
                    
                else
                    linea_construida+=" "
                fi
            else
                # MODO "PLANETA".
                local char="${linea_planeta:j:1}"
                if [[ -z "$char" ]]; then char=" ";fi
                
                if [[ "$char" == "█" || "$char" == "#" ]];then linea_construida+="${c_tierra_planeta}${char}${c_reset}"
                elif [[ "$char" == "-" || "$char" == "=" || "$char" == ":" || "$char" == "." ]];then linea_construida+="${c_borde_planeta}${char}${c_reset}"
                else linea_construida+="${c_agua_planeta}${char}${c_reset}";fi
            fi
        done
        buffer+="$linea_construida"
    done

    # PACMAN.
    local -n ref_p
    if [[ $estado_pac -eq 0 ]];then ref_p=pac_cerrado
    elif [[ $pac_dir -eq 1 ]];then ref_p=pac_abierto_der
    else ref_p=pac_abierto_izq;fi

    local j=0
    
    for pline in "${ref_p[@]}";do
        local py=$((pac_y + j))
        local linea_transparente="${pline// /\\e[1C}"
    
        if [[ $py -gt 0 ]] && [[ $py -lt $alto_term ]];then
            buffer+="\e[${py};${pac_x}H${c_pacman}${linea_transparente}${c_reset}"
        fi
        
        ((j++))
    done
    
    echo -ne "$buffer"
}

# BUCLE PRINCIPAL.
ultimo_tiempo=$(date +%s%N)
temp_mov=0
temp_anim=0

while true;do
    ahora=$(date +%s%N)
    delta=$((ahora - ultimo_tiempo))
    ultimo_tiempo=$ahora
    
    # LÓGICA DE MOVIMIENTO (ESTADO 0).
    if [[ $estado_juego -eq 0 ]];then
        temp_mov=$((temp_mov + delta))
        
        if [[ $temp_mov -ge $vel_movimiento ]];then
            pac_x=$((pac_x + pac_dir))
            actualizar_mascara

            # Límites zigzag.
            if [[ $pac_dir -eq -1 ]] && [[ $pac_x -le $((offset_x - limite_pacman_izq)) ]];then
                pac_dir=1;pac_y=$((pac_y - 4));pac_x=$((offset_x - limite_pacman_izq))
            fi
            
            if [[ $pac_dir -eq 1 ]] && [[ $pac_x -ge $((offset_x + ancho_planeta + limite_pacman_der)) ]];then
                pac_dir=-1;pac_y=$((pac_y - 4));pac_x=$((offset_x + ancho_planeta + limite_pacman_der))
            fi
            
            # FIN DEL RECORRIDO (Salida del script).
            if [[ $pac_y -le $((offset_y + 2)) ]] && [[ $pac_dir -eq 1 ]] && [[ $pac_x -ge $((offset_x + ancho_planeta)) ]];then
                 tput cnorm;echo -e "\e[H\e[2J\e[0m";exit 0
            fi
            
            temp_mov=0
            dibujar_escena
        fi
    fi
    
    # Acumular tiempo para la animación.
    temp_anim=$((temp_anim + delta))

    # Si pasó el tiempo suficiente, cambiar estado (0 a 1, o 1 a 0, etc).
    if [[ $temp_anim -ge $vel_anim ]];then
        estado_pac=$((1 - estado_pac))
        temp_anim=0
    fi
    
    sleep 0.001 # Descanso de CPU para no recargarla innecesariamente.
done

Análisis detallado: cómo funciona el motor de borrado del planeta y revelado del logotipo.

Antes de ejecutar el código extraído en la sección anterior, es imperativo aclarar un requisito físico. Aunque la lógica del script es autónoma, sus dependencias son los archivos de gráficos ASCII. Para que el programa funcione y no acuse errores del tipo "falta el directorio arte_ascii, o falta el archivo pacman", debes tener una carpeta llamada arte_ascii en el mismo directorio en donde guardes el script. Dentro de ella, deben existir los siguientes archivos de texto plano que contienen los dibujos: planeta, logo_entropía, pacman_cerrado, pacman_abierto_izq y pacman_abierto_der. Sin estos archivos, las matrices de memoria estarán vacías y el script no tendrá nada que dibujar.

Una vez cubierto este requisito, pasemos al análisis técnico de cómo BASH, un lenguaje pensado para administrar sistemas, es llevado aquí a comportarse como un excelente motor de videojuegos.

1. La carga de recursos por referencia.

El script comienza definiendo colores y variables, pero la primera pieza de ingeniería interesante es la función cargar_sprite. En lugar de escribir el código de carga cinco veces (una para cada archivo), se utiliza una función general. Lo notable aquí es el uso de local -n. Esto crea una "nameref" (referencia por nombre), que actúa como un puntero. Cuando llamamos a cargar_sprite "planeta" arte_planeta ..., la función no recibe una copia del array, sino que apunta directamente a la variable global arte_planeta. El comando mapfile -t es el encargado de leer el archivo de texto línea por línea y volcarlo directamente dentro del array. Sin este comando, tendríamos que utilizar, por ejemplo, bucles while read que son mucho más lentos.

2. El sistema de coordenadas relativas.

Para centrar el planeta en la pantalla, el script calcula el offset_y y offset_x basándose en el tamaño actual de la terminal (tput cols). Sin embargo, el reto matemático está en el logotipo. El logo de "Entropía binaria" es algo más pequeño que el planeta y debe aparecer centrado dentro de él. Para esto se calculan logo_rel_y y logo_rel_x. Estas variables almacenan la diferencia de posición entre la esquina superior izquierda del planeta y la esquina superior izquierda del logo. Este cálculo es fundamental para que más adelante sepamos exactamente qué píxel del logo corresponde a qué píxel del planeta.

3. El corazón del efecto: declare -A visitados.

Esta línea es muy importante, porque al declarar visitados como un array asociativo (-A), estamos creando una memoria no dependiente de índices numéricos consecutivos (0, 1, 2...), sino de claves de texto. El script utiliza este array como una "máscara de recorte". No guardamos el dibujo final, sino simplemente un registro de coordenadas "Y,X" por las que el Pacman ha pasado. Si la clave "5,10" existe en el array, significa que esa celda ya fue "comida".

4. La función actualizar_mascara.

Cada vez que el Pacman se mueve un paso, se llama a esta función. Su trabajo no es dibujar, sino calcular colisiones. El script recorre el cuadrado pequeño que forma el cuerpo del Pacman. Si el Pacman tiene un píxel sólido en una posición, el script traduce esa posición local a la posición global del planeta. La línea visitados["$ly,$lx"]=1 es la que "marca" el territorio. Nótese que no borramos el planeta en este punto; simplemente anotamos en nuestra libreta virtual que esa coordenada específica ya ha sido visitada.

5. El renderizado condicional (dibujar_escena).

Aquí es en donde ocurre la magia visual. El script no dibuja el logo y luego le pega el "planeta comido parcialmente" encima: esto posiblemente causaría parpadeos que el ojo humano sería capaz de advertir. En su lugar, decide píxel por píxel qué debe mostrar, componiendo una imagen que será la que se espere ver (planeta mezclado con logo de manera inteligente y razonable. El bucle anidado recorre todas las filas (i) y columnas (j) del área del planeta. En cada iteración, se hace una pregunta crítica: if [[ "${visitados["$i,$j"]}" == "1" ]]; ...

- Si la respuesta es SÍ (1): significa que el Pacman ya pasó por ahí. El script ignora el array del planeta y mira el array arte_logo. Utilizando las coordenadas relativas calculadas al principio, extrae el color y el caracter del logo y lo añade al búfer.
/ Si la respuesta es NO (vacío): significa que el terreno está intacto. El script ignora el logo y extrae el carácter del array arte_planeta, aplicándole los colores de tierra o agua.

Este enfoque permite que el logo aparezca "detrás" del planeta a medida que este es borrado, creando una sensación de profundidad y capas.

6. El bucle de física y el movimiento zigzag.

Finalmente, el bucle while true maneja el tiempo. Se usa la técnica de "Delta tiempo" (ahora - ultimo_tiempo) para controlar la velocidad. Esto asegura que el juego corra a la misma velocidad independientemente de la potencia de la CPU que esté ejecutando el script, cosa que no sucede con "sleep". El movimiento en zigzag se logra monitoreando la variable pac_x.

Cuando pac_dir es -1 (izquierda) y se llega al limite_pacman_izq, el script cambia pac_dir a 1 (derecha) y resta 4 a pac_y.

Al restar a pac_y, el personaje sube visualmente porque en la terminal, la fila 0 está arriba. Este cambio de dirección y altura crea el patrón de barrido que permite limpiar todo el mapa automáticamente hasta que la coordenada Y del Pacman supera la parte superior del planeta, momento en el cual el script detecta el fin del juego y termina, así como termina este artículo :D

 

Generación procedimental de entornos en terminal: anatomía de un cielo estrellado en BASH.


Generación procedimental de entornos en terminal: anatomía de un cielo estrellado en BASH.

Análisis del script de presentación ASCII de los videos de Entropía binaria para 2026. 

Cuando nos enfrentamos al desafío de crear videojuegos o animaciones en la terminal utilizando BASH, uno de los primeros obstáculos es dar profundidad a la escena. Un fondo negro plano puede resultar aburrido y bidimensional, mientras que un fondo dinámico o vistoso siempre aporta vida y contexto. En el script analizado, la solución para crear una atmósfera espacial es la implementación de un sistema de partículas estáticas simulando un campo de estrellas. A diferencia de dibujar un simple archivo de texto como fondo, este método genera las estrellas matemáticamente, lo que significa que cada vez que ejecutamos el programa, el cielo es único. Para lograr esto, no podemos simplemente imprimir caracteres al azar en un bucle infinito; necesitamos persistencia de datos.

El primer concepto a asimilar es que, en una animación en donde la pantalla se refresca constantemente (se borra y se redibuja), las estrellas no pueden cambiar de lugar en cada fotograma. Si generáramos coordenadas aleatorias dentro del bucle de dibujo, el fondo parecería estática de televisión. Por lo tanto, el paso fundamental ocurre antes de que empiece el juego, en la fase de inicialización. El script utiliza lo que llamamos matrices paralelas ("arrays" paralelos). En lugar de tener un "objeto estrella", BASH nos permite crear varias listas: una para la posición X, otra para la posición Y, una tercera para el carácter que representa la estrella y una última para su color. De este modo, el índice 0 de todos los arrays define las propiedades de la primera estrella, el índice 1 las de la segunda, y así sucesivamente hasta llegar al número total definido en la variable num_estrellas.

Para distribuir estos puntos luminosos, necesitamos conocer los límites de nuestro lienzo (terminal). El script hace uso del comando tput para obtener dinámicamente el ancho (cols) y el alto (lines) de la terminal actual. Dentro de un bucle for, utilizamos la variable interna $RANDOM combinada con el operador módulo (%) sobre estas dimensiones. Esto nos devuelve un número entre 0 y el tamaño de la pantalla, garantizando que ninguna estrella se genere fuera del área visible. Es aquí en donde entra en juego la variedad visual. Un cielo realista no se compone solo de puntos idénticos. El código define dos matrices (arrays) de caracteres: uno de chars_comunes (como puntos y comas) y otro de chars_raros (como asteriscos o círculos). Mediante una probabilidad simple, definida en prob_estrella_rara, el script decide si la estrella actual será un simple punto de luz distante o un cuerpo celeste más brillante, asignándole además un color aleatorio de una paleta ANSI predefinida.

Una vez que tenemos los datos almacenados en memoria, el desafío técnico se desplaza al motor de renderizado, específicamente dentro de la función dibujar_escena. Aquí es donde la optimización se vuelve crítica. BASH no es, en principio, un lenguaje rápido para dibujar gráficos, por lo tanto, dibujar cientos de elementos innecesarios podría llegar a causar parpadeos. El script implementa una técnica de "enmascaramiento" o "culling" muy interesante. Antes de agregar una estrella al búfer de dibujo, verifica si sus coordenadas caen dentro del rectángulo que ocupa el planeta (o el logo que no se ve al principio, porque está posicionado por detrás del planeta Tierra). Si la coordenada X e Y de la estrella están dentro de los límites del objeto principal (offset_x y offset_y), el script ejecuta un continue y salta esa iteración. Esto evita que se dibujen estrellas por encima del planeta, creando la ilusión de que el cuerpo celeste está en primer plano y las estrellas en el fondo, sin necesidad de complicados cálculos de capas Z (profundidad).

Finalmente, la materialización de todo este cálculo matemático en la pantalla se realiza mediante códigos de escape ANSI. No se utiliza el comando echo línea por línea, ya que esto generaría un proceso demasiado lento. En su lugar, se construye una variable gigante llamada buffer. Para cada estrella válida, se concatena una secuencia de escape que mueve el cursor directamente a la posición deseada (\e[Y;XH), establece el color, imprime el caracter y resetea el color. Al final del ciclo de dibujo, se imprime todo el búfer de una sola vez. Este enfoque está basado en que en la programación de terminal, el control preciso del cursor y la gestión eficiente de cadenas de caracteres y del tiempo de dibujado en pantalla son las herramientas que transforman simples datos numéricos en un universo visualmente atractivo. BASH posee límites inexplorados e infravalorados en cuanto a sus capacidades de dibujo.

Disección del código: implementación práctica del campo estelar.

Para comprender verdaderamente cómo funciona el sistema, no hay nada mejor que aislar este mecanismo del resto del juego. A continuación se presenta el código fuente extraído directamente del script original, limpio de cualquier lógica ajena al cielo estrellado (como el Pacman o el planeta), pero manteniendo intacta la matemática y la sintaxis que generan las estrellas. Este fragmento es totalmente funcional: puedes copiarlo, guardarlo (por ejemplo) como "estrellas.sh", darle permisos de ejecución y verás el mismo fondo estelar que en el juego.


#!/bin/bash

# CONFIGURACIÓN.
num_estrellas=100
prob_estrella_rara=10
c_reset="\e[0m"

# DEFINICIÓN DE CARACTERES Y COLORES.
chars_comunes=("." "·" "," "°")
chars_raros=("*" "+" "o" "O" "ø" "●")
colores_estrellas=("\e[34m" "\e[36m" "\e[33m" "\e[37m" "\e[31m" "\e[90m")

# DECLARACIÓN DE MATRICES PARALELAS (ARRAYS PARALELOS).
declare -a estrellas_x estrellas_y estrellas_char estrellas_color

# FUNCIÓN DE INICIALIZACIÓN (lógica exacta del script original).
iniciar_estrellas() {
    local ancho_term=$(tput cols)
    local alto_term=$(tput lines)

    for ((i=0;i<num_estrellas;i++));do
        # Posición aleatoria.
        estrellas_x[i]=$((RANDOM % ancho_term))
        estrellas_y[i]=$((RANDOM % alto_term))

        # Elegir caracter (común vs raro).
        if [[ $((RANDOM % prob_estrella_rara)) -eq 0 ]];then
            # Estrella rara.
            local idx=$((RANDOM % ${#chars_raros[@]}))
            estrellas_char[i]="${chars_raros[$idx]}"
        else
            # Estrella común (punto, coma, etc).
            local idx=$((RANDOM % ${#chars_comunes[@]}))
            estrellas_char[i]="${chars_comunes[$idx]}"
        fi

        # Elegir color aleatorio.
        local c_idx=$((RANDOM % ${#colores_estrellas[@]}))
        estrellas_color[i]="${colores_estrellas[$c_idx]}"
    done
}

# PREPARACIÓN DE PANTALLA.
tput civis;clear

# EJECUCIÓN.
iniciar_estrellas

# RENDERIZADO (extraído del bucle de dibujo principal).
buffer=""
for ((s=0; s<num_estrellas; s++));do
    sx=${estrellas_x[s]}
    sy=${estrellas_y[s]}
    
    # Agregar al búfer de dibujo.
    buffer+="\e[${sy};${sx}H${estrellas_color[s]}${estrellas_char[s]}${c_reset}"
done 

echo -ne "$buffer"

# FINALIZAR (esperar tecla para salir y restaurar cursor).
read -n 1 -s
tput cnorm;clear

 

Explicación línea por línea.

El script comienza con el clásico #!/bin/bash, indicando al sistema qué intérprete usar. Inmediatamente después, se definen las variables de control: num_estrellas=100 establece la cantidad de partículas y prob_estrella_rara=10 dicta que hay una probabilidad de 1 entre 10 de que aparezca una estrella especial. c_reset es simplemente el código de escape para limpiar el color al final de cada impresión.

A continuación, vemos la definición de los "activos" gráficos. chars_comunes y chars_raros son listas (matrices, arrays) que contienen los "glifos" (símbolos visuales de caracteres) que veremos en pantalla. colores_estrellas almacena los códigos ANSI para azul, cian, amarillo, blanco, rojo y gris. Luego, declare -a inicializa cuatro matrices (arrays) vacías (estrellas_x, estrellas_y, etc.). Estos serán nuestros contenedores de datos, los cuales es de buen proceder declararlos explícitamente cuando se trabaja con estructuras indizadas en BASH, para evitar comportamientos inesperados.

Entramos en la función iniciar_estrellas. Las dos primeras líneas dentro de la función usan tput cols y tput lines. Esto es dinámico: pregunta al sistema el tamaño actual de la ventana en el momento de la ejecución. Luego inicia un bucle for que se repetirá 100 veces (el valor de num_estrellas).

Dentro del bucle, estrellas_x[i]=$((RANDOM % ancho_term)) genera la coordenada horizontal. Al usar el módulo (%) sobre el ancho de la terminal, obtenemos un valor que siempre caerá dentro de la pantalla. Lo mismo ocurre con la coordenada "Y". El bloque if siguiente decide el aspecto: genera un número al azar entre 0 y 9. De ser 0 (probabilidad del 10%), entra en el bloque de estrellas raras, elige un índice aleatorio basado en la longitud de la matriz ${#chars_raros[@]} y asigna ese carácter a estrellas_char[i]. Si no es 0, hace lo mismo pero con la matriz de caracteres comunes. Finalmente, se selecciona un color al azar de la lista de colores disponibles.

Al salir de la función, encontramos tput civis;clear. civis oculta el cursor parpadeante (esencial para que se vea estético) y clear limpia la pantalla. Llamamos a la función iniciar_estrellas para llenar las matrices con datos.

El bloque final es el motor de renderizado. Creamos una variable vacía llamada buffer. Recorremos nuevamente los 100 glifos (las 100 estrellas) con un bucle. Extraemos las coordenadas X e Y guardadas en memoria. La línea clave es buffer+="\e[${sy};${sx}H...". Aquí estamos concatenando una cadena de texto que contiene una instrucción para mover el cursor a la posición Y, X guardada, luego aplica el color guardado, imprime el caracter guardado y resetea el color.

Finalmente, echo -ne "$buffer" vuelca toda esa información acumulada en la pantalla de un solo golpe. El comando read -n 1 -s (el cual se puede escribir también, por ejemplo, "read -sn1") pausa el script esperando que pulses una tecla, y tput cnorm restaura el cursor a modo visible antes de salir para no dejar a la terminal sin su cursor.

Flag Counter Visitas previas a la existencia de este contador: 3433

Artículos aleatorios

    Páginas: