C

Ejercicios en C

La mejor forma de aprender C es resolviendo problemas reales. Acá vas a encontrar ejercicios prácticos que combinan lógica, punteros, bucles y funciones del sistema.

¿Por qué ejercicios?

Leer código no alcanza. La programación se aprende haciendo. Cada ejercicio te fuerza a pensar en bucles, memoria y la lógica de bajo nivel que hace a C único.

Aprendizaje Práctico

Cada ejercicio incluye el resultado visual para que sepas qué tenés que lograr, el código completo y una explicación paso a paso.

01. El Caso: Cascada Matrix

El objetivo es recrear el efecto visual de la lluvia de caracteres de Matrix en la consola de Windows. Columnas de caracteres verdes cayendo a diferentes velocidades con un efecto de desvanecimiento.

Abajo podés ver una simulación en vivo del resultado esperado. Tu código en C debería producir algo similar en la consola de Windows.

C:\> matrix.exe — Resultado esperado

Lenguaje

C (Windows API)

Dificultad

⭐⭐ Intermedio

Conceptos

Bucles, Arrays, Random, Console API

02. Pistas y Herramientas

Antes de ver la solución, intentá pensarlo por tu cuenta. Acá te dejamos las herramientas (librerías y funciones del sistema de Windows) que te van a hacer falta.

Librerías sugeridas

stdlib.h para números aleatorios, y windows.h para controlar la posición de la consola.

Funciones que vas a necesitar

Vas a tener que desarrollar una función gotoxy(x, y) usando SetConsoleCursorPosition para ubicar los caracteres en pantalla.

03. Código Resuelto

Una vez que lo hayas intentado, podés revelar la solución acá abajo. Copialo, compilalo con gcc matrix.c -o matrix y ejecutalo.

Código Censurado

No hagas trampa. Intentá resolverlo primero con las pistas de arriba.

matrix.c
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <time.h>

#define MAX_COLS 300  // Máximo de columnas soportadas

HANDLE hConsole;
int ANCHO, ALTO;  // Se detectan automáticamente

// Detecta el tamaño real de la ventana de consola
void obtenerTamano() {
    CONSOLE_SCREEN_BUFFER_INFO info;
    GetConsoleScreenBufferInfo(hConsole, &info);
    
    // Tamaño de la ventana visible
    ANCHO = info.srWindow.Right - info.srWindow.Left + 1;
    ALTO  = info.srWindow.Bottom - info.srWindow.Top + 1;

    // FORZAR que el buffer de memoria sea igual al de la ventana
    // Esto elimina los scrollbars por definición
    COORD size;
    size.X = (short)ANCHO;
    size.Y = (short)ALTO;
    SetConsoleScreenBufferSize(hConsole, size);

    // Reducimos ALTO y ANCHO para el dibujo para que nunca toque los bordes
    ALTO -= 2; 
    ANCHO--;
}

// Mueve el cursor a la posición (x, y) de la consola
void gotoxy(int x, int y) {
    COORD coord = {x, y};
    SetConsoleCursorPosition(hConsole, coord);
}

// Cambia el color del texto en la consola
void setColor(int color) {
    SetConsoleTextAttribute(hConsole, color);
}

// Genera un largo de rastro proporcional a ALTO (entre 25% y 75%)
int largoAleatorio() {
    int min = ALTO / 4;
    int rango = ALTO / 2;
    if (min < 3) min = 3;
    if (rango < 3) rango = 3;

    // Hay una regla matemática mágica: El resto de una división por "N" siempre será un número entre 0 y N-1.
    return min + rand() % rango;
}

int main() {
    int cabeza[MAX_COLS];
    int largo[MAX_COLS];
    int velocidad[MAX_COLS];
    int tick = 0, i;

    hConsole = GetStdHandle(STD_OUTPUT_HANDLE);

    // Ocultar el cursor
    CONSOLE_CURSOR_INFO cursorInfo = {1, FALSE};
    SetConsoleCursorInfo(hConsole, &cursorInfo);

    // Limpiar pantalla al iniciar
    system("cls");
    srand(time(NULL));

    // Detectar el tamaño real de la consola
    obtenerTamano();
    if (ALTO <= 0) ALTO = 1;
    if (ANCHO <= 0) ANCHO = 80;

    // Limitar a MAX_COLS por seguridad
    if (ANCHO > MAX_COLS) ANCHO = MAX_COLS;

    // Inicializar: posiciones negativas = delay antes de aparecer
    for (i = 0; i < ANCHO; i++) {
        int divisorIni = (ALTO > 0) ? ALTO : 1;
        cabeza[i]    = -(rand() % divisorIni);
        largo[i]     = largoAleatorio();
        velocidad[i] = 1 + rand() % 3;
    }

    while (1) {
        tick++;
        for (i = 0; i < ANCHO; i++) {
            // Solo avanzar si toca según la velocidad
            if (tick % velocidad[i] != 0)
                continue;

            int y = cabeza[i];
            int cola = y - largo[i];

            // 1. CABEZA: blanco brillante
            if (y >= 0 && y < ALTO) {
                gotoxy(i, y);
                setColor(15);
                printf("%c", 33 + rand() % 90);
            }

            // 2. RASTRO: el anterior pasa a verde claro
            if (y - 1 >= 0 && y - 1 < ALTO) {
                gotoxy(i, y - 1);
                setColor(10);
                printf("%c", 33 + rand() % 90);
            }

            // 3. FADE 1: a un tercio pasa a verde oscuro
            int fade1 = y - (largo[i] / 3);
            if (fade1 >= 0 && fade1 < ALTO) {
                gotoxy(i, fade1);
                setColor(2);
                printf("%c", 33 + rand() % 90);
            }

            // 4. FADE 2: a dos tercios pasa a gris oscuro
            int fade2 = y - (largo[i] * 2 / 3);
            if (fade2 >= 0 && fade2 < ALTO) {
                gotoxy(i, fade2);
                setColor(8);
                printf("%c", 33 + rand() % 90);
            }

            // 5. BORRAR: la cola desaparece
            if (cola >= 0 && cola < ALTO) {
                gotoxy(i, cola);
                printf(" ");
            }

            // Avanzar siempre de a 1 fila
            cabeza[i]++;

            // Reiniciar cuando la cola sale de pantalla
            if (cola >= ALTO) {
                int divisor = (ALTO / 2 > 0) ? (ALTO / 2) : 1;
                cabeza[i]    = -(rand() % divisor);
                largo[i]     = largoAleatorio();
                velocidad[i] = 1 + rand() % 3;
            }
        }

        gotoxy(0, 0);
        Sleep(30);  // ~33 FPS
    }

    return 0;
}

04. Planteamiento y Explicación

Acá te contamos cómo pensamos la solución paso a paso.

1

Librerías necesarias

Usamos stdio.h para printf, stdlib.h para rand() y srand(), windows.h para controlar la consola (posición del cursor, colores) y time.h para la semilla aleatoria.

⚠️ windows.h es exclusivo de Windows. En Linux usarías ANSI escape codes.

2

Funciones auxiliares

obtenerTamano() usa GetConsoleScreenBufferInfo para detectar el tamaño real de la consola (columnas y filas). Así el programa se adapta a cualquier tamaño de ventana.

largoAleatorio() genera un largo de rastro proporcional al ALTO de la consola: entre 25% y 75% de la altura. Esto asegura que los rastros se vean bien sin importar si la consola tiene 25 o 50 filas.

gotoxy(x, y) mueve el cursor y setColor(n) cambia el color (donde 15 = blanco, 10 = verde, 2 = verde oscuro).

3

Los arrays: cabeza[], largo[] y velocidad[]

Cada posición del array representa una columna de la consola. Los arrays se dimensionan con MAX_COLS (300) como tope, pero solo se usan las primeras ANCHO > posiciones (el ancho real de la consola). cabeza[i] guarda la fila actual de la punta, largo[i] es proporcional al ALTO (25-75%), y velocidad[i] controla cada cuántos ticks avanza (1 = rápido, 3 = lento). Inicializar con valores negativos (hasta -ALTO) crea un delay proporcional antes de que la columna aparezca.

Hay una regla matemática mágica: El resto de una división por "N" siempre será un número entre 0 y N-1.

4

El efecto de degradado

Como cada columna siempre avanza de a 1 fila, podemos pintar 4 capas por columna en cada update sin dejar huecos:

💡 Cabeza

Blanco brillante (color 15) — la punta que cae en y.

🟢 Rastro

Verde claro (color 10) — en y-1 (la que era blanca el frame anterior).

🌑 Fade 1

Verde oscuro (color 2) — en un tercio del rastro (y - largo/3).

🌑 Fade 2

Gris oscuro (color 8) — casi al final del rastro (y - largo*2/3).

⬛ Borrado

Espacio vacío — en y - largo, la cola que se borra.

5

Velocidad sin saltar filas

El truco clave: cada columna avanza siempre de a 1 fila, pero NO todas avanzan en cada tick. Usamos tick % velocidad[i] != 0 para que las columnas "lentas" (velocidad 2-3) se salteen ticks. Así no quedan huecos blancos.

💡 ¿Por qué no sumar la velocidad a la posición? Si la cabeza salta de fila 5 a fila 8, la fila 5 queda blanca para siempre porque nunca se recolorea a verde. Avanzar de a 1 garantiza que cada posición blanca sea cambiada a verde en el siguiente update.

6

El reinicio cíclico

Cuando la cola (cabeza - largo) supera ALTO, significa que todo el rastro ya salió de la pantalla. Ahí reiniciamos con cabeza = -(rand() % 20) — el valor negativo crea un delay aleatorio antes de que la nueva cascada aparezca, para que no todas las columnas se reinicien al mismo tiempo.

7

Evitando el Scroll (El gran bug)

Si el programa intenta imprimir en la última fila o columna, la terminal de Windows hace scroll automático, rompiendo la animación.

La solución: Primero, restamos ALTO -= 2 y ANCHO-- para dejar un margen de seguridad. Segundo, NUNCA uses system("mode con..."), ya que desincroniza el tamaño físico del buffer lógico, causando wraps invisibles que fuerzan el scroll incluso si crees estar dentro de los límites.

¿Cómo compilar y ejecutar?

$ gcc matrix.c -o matrix

$ ./matrix

Si usás Dev-C++ o Code::Blocks, simplemente creá un nuevo proyecto de tipo "Console Application", pegá el código y presioná F9 para compilar y ejecutar.