Bouncing an Asterisk

For last week’s Lesson, I gathered various techniques to show how the terminal screen can be manipulated directly in C without using a library like Ncurses. I have a few more tricks to show.

The next item I’d like to pull from old code is the kbhit() function, once popular in C programming for older (non-multitasking) operating systems. I wrote about emulating this function in another post. As with various terminal manipulation magic, recreating the kbhit() function involves a call to the ioctl() function to peek into the terminal’s guts. You can read the details in the original post.

My purpose for adding the kbhit() function is to recreate the old bouncing asterisk program, which was popular with budding programmers way back when. The effect is seen as a character that traverses the screen, changing directions when it bumps into the screen’s edge.

The kbhit() function is necessary to poll the keyboard to check for a press of the Enter key, which terminates the program. I also updated the code presented in last week’s Lesson, modifying the locate() function to not only move the cursor but to place a character at the given position. The new function is called putat(), and it’s shown in this update to the source code file:

2024_10_05-Lesson.c

#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <sys/ioctl.h>

#define home() printf("\e[H")
#define clear() printf("\e[H\e[2J")

/* check for a key waiting */
int kbhit(void)
{
    int k;

    ioctl(STDIN_FILENO,FIONREAD,&k);

    return(k);
}

/* set the cursor to position x (columns)
   and y (rows ) and set character c */
void putat(x,y,c)
{
    printf("\e[%d;%dH%c",y,x,c);
}

/* pause for m milliseconds */
void delay(int m)
{
    long pause;
    clock_t now,then;

    pause = m*(CLOCKS_PER_SEC/1000);
    now = then = clock();
    while( (now-then) < pause )
        now = clock();
}

/* set an asterisk in the center of the screen */
int main()
{
    int rows,columns,posx,posy,vert,horz;
    struct winsize w;
    char buffer[BUFSIZ];

    /* obtain the window size */
    ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
    rows = w.ws_row;
    columns = w.ws_col;

    /* remove line buffering */
    setvbuf(stdout,buffer,_IONBF,BUFSIZ);

    /* initialize */
    clear();
    puts("Press Enter to end");
    posx=columns/2;
    posy=rows/2;
    vert=1;
    horz=1;

    /* bounce the asterisk */
    while(1)
    {
        /* end the loop on key press */
        if( kbhit() )
        {
            getchar();
            break;
        }
        /* position the cursor */
        putat(posx,posy,'*');
        delay(125);                /* 1/8 sec. delay */
        putat(posx,posy,' ');    /* erase */
        /* check bounds */
        if( posx==columns-1 || posx==1 )
            horz=-horz;            /* switch directions */
        posx += horz;
        if( posy==rows-1 || posy==2 )
            vert=-vert;            /* switch directions */
        posy += vert;
    }
    /* set the cursor and say goodbye */
    putat(1,rows-1,'B');
    puts("ye!");

    return 0;
}

This code adds four new int variables to track the bouncing asterisk: posx, posy, vert, and horz. The posx and posy variables contain the asterisk’s column and row positions. The vert and horz variables hold 1 or -1 to set the direction.

The bouncing action takes place in the endless while loop. The kbhit() function terminates the loop when the user presses the Enter key. (It’s not a perfect substitute for the original kbhit() as other characters entered appear in the output.)

Within the loop, the asterisk is positioned, execution delays for 125 milliseconds, then the asterisk is erased. An if test checks for the edges of the terminal window. When encountered, the horz or vert variables are inverted, changing the asterisk’s direction. This action repeats until the user presses Enter.

Figure 1 shows a sample run.

Figure 1. The program’s sample run.

I continue messing with these features in next week’s Lesson.

Leave a Reply