1
\$\begingroup\$

This program is written for Windows 7 under MinGW using gcc.

I'm seeking recommendations for improving the program's portability.

/* galtonboard.c

   Galton Board Simulation (random mechanical cascade)

   notes: rate determines how often a ball is dropped
   e.g. 
       rate = 1  continuous; 1 ball on each row; up to 39 balls on the board
                 at once
            = 3  1 ball every 3 rows; up to 13 balls on the board at a time;
                 matches the vertical spacing of the pins
            = 39 1 ball every 39 rows; 1 ball on the board at a time
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <time.h>

#define WIDTH           80
#define RTMARGIN        78
#define CENTER          39
#define NUMBEROFROWS    39
#define NUMBEROFBINS    13
#define PINGRAPHIC      "^"
#define BALLGRAPHIC     "*"
#define SPACEGRAPHIC    " "
#define BARGRAPHIC      "\xdb\xdb\xdb\xdb\xdb"

struct ballData
{
    int col;
    int dir;
};

void drawNumber (char scr[], const int row, const int col, int number)
{
    int position = row * WIDTH + col;

    do {
         scr[position] = number % 10 + '0';
        --position;
        number /= 10;
    }
    while ( number > 0 );
}

void drawString (char scr[], const int row, const int col,
     const char * const str)
{
    memcpy (&scr[row * WIDTH + col], str, strlen (str));
}

void checkBallInBin (char scr[], const int col, int * const count, int bin[])
{
    if ( col > 0 ) {
        ++*count;
        int binNumber = col / 6;
        ++bin[binNumber];
        drawNumber (scr, NUMBEROFROWS + 1, binNumber * 6 + 5,
                   bin[binNumber]);
    }
}

void checkNewBall (char scr[], const int col, int * const count,
     const int nBalls, struct ballData board[])
{
    if ( *count < nBalls ) {
        ++*count;
        drawNumber (scr, 2, col, *count);
        board[0].col = CENTER;
    }
}

void clearScreen (void)
{
    system ("cls");
}

void delay (const int interval)
{
    Sleep (interval);
}

void displayScreen (const char scr[])
{
    puts (scr);
}

int numberLen (int number)
{
    int digitCount = 0;

    do {
        ++digitCount;
        number /= 10;
    }
    while ( number > 0 );

    return digitCount;
}

void drawPercent (char scr[], const int row, int col, int number)
{
    drawString (scr, row, col, "%");
    --col;

    int tenthsDigit = number % 10;
    number /= 10;

    if ( number != 100 ) {
        drawNumber (scr, row, col, tenthsDigit);
        --col;
        drawString (scr, row, col, ".");
        --col;
    }    

    drawNumber (scr, row, col, number);
}

void drawHistogram (char scr[], const int bin[])
{
    for ( int i = 2; i <= NUMBEROFROWS - 2; ++i )
        memset (scr + i * WIDTH + 1, ' ', RTMARGIN - 1);

    drawString (scr, 2, 1, "frequency histogram");

    int sum = 0;
    for ( int i = 0; i < NUMBEROFBINS; ++i )
        sum += bin[i];    
    drawString (scr, 2, RTMARGIN - 1 - numberLen (sum) - 8, "\xe4 bins =");
    drawNumber (scr, 2, RTMARGIN - 1, sum);

    // histogram height at 100 percent = 34 rows
    // constant of proportionality -- fraction of rows representing 1 %
    const float k = 34 / 100.0;

    for ( int i = 0; i < NUMBEROFBINS; ++i ) {
        float frequency = 100.0 * bin[i] / sum;
        int barHeight = (int) (k * frequency);
        for ( int j = 0; j < barHeight; ++j )
            drawString (scr, NUMBEROFROWS - 1 - j, i * 6 + 1, BARGRAPHIC);
        drawPercent (scr, NUMBEROFROWS - 1 - barHeight, i * 6 + 5,
                    frequency * 10);
    }
}

void drawInitialScreen (char scr[], const int nBalls)
{
    drawString (scr, 0, 1, "Galton Board Simulation");

    for ( int i = 1; i < CENTER - 1; ++i ) {
        drawString (scr, 1, i, "\xc4");
        drawString (scr, 1, i + CENTER + 1, "\xc4");
    }
    drawString (scr, 1, 0, "\xda");
    drawString (scr, 1, RTMARGIN, "\xbf");
    drawString (scr, 1, CENTER - 1, "\xd9");
    drawString (scr, 1, CENTER + 1, "\xc0");

    for ( int i = 2; i <= NUMBEROFROWS - 2; ++i ) {
        drawString (scr, i, 0, "\xb3");
        drawString (scr, i, RTMARGIN, "\xb3");
    }

    for ( int i = 0; i < NUMBEROFBINS; ++i ) {
        drawString (scr, NUMBEROFROWS - 1, i * 6, "\xb3");
        drawString (scr, NUMBEROFROWS, i * 6 + 1,
                   "\xc4\xc4\xc4\xc4\xc4\xc5");
        drawString (scr, NUMBEROFROWS + 1, i * 6, "\xb3");
        drawString (scr, NUMBEROFROWS + 2, i * 6 + 1,
                   "\xc4\xc4\xc4\xc4\xc4\xc1");
    }
    drawString (scr, NUMBEROFROWS - 1, RTMARGIN, "\xb3");
    drawString (scr, NUMBEROFROWS, 0, "\xc3");
    drawString (scr, NUMBEROFROWS, RTMARGIN, "\xb4");
    drawString (scr, NUMBEROFROWS + 1, RTMARGIN, "\xb3");
    drawString (scr, NUMBEROFROWS + 2, 0, "\xc0");
    drawString (scr, NUMBEROFROWS + 2, RTMARGIN, "\xd9");

    drawString (scr, 2, 1, "random mechanical cascade");
    drawString (scr, 2, RTMARGIN - 1 - numberLen (nBalls) - 2, "of");
    drawNumber (scr, 2, RTMARGIN - 1, nBalls);

    for ( int i = 0; i < NUMBEROFBINS - 1; ++i ) {
        for ( int j = 0; j <= i; ++j )
            drawString (scr, (i + 1) * 3,  CENTER - i * 3 + j * 6,
                       PINGRAPHIC);
    }

    for ( int i = 0; i < NUMBEROFBINS; ++i )
        drawNumber (scr, NUMBEROFROWS + 1, i * 6 + 5, 0);
}

void drawOrEraseBalls (char scr[], const struct ballData board[],
     const char * const str)
{
    for ( int i = 0; i < NUMBEROFROWS; ++i ) {
        if ( board[i].col > 0 )
            drawString (scr, i, board[i].col, str);
    }                
}

int randomNumber (const int range)
{
    // return an integer between 0 and range - 1
    return rand() % range;
}

void moveBallsDown (struct ballData board[])
{
    for ( int i = NUMBEROFROWS - 2; i >= 0; --i ) {
        if ( board[i].col > 0 ) {
            if ( i % 3 == 2 ) {
                // pin
                if ( randomNumber (2) == 0 )
                    board[i].dir = -1;
                else
                    board[i].dir = 1;
            }
            board[i].col += board[i].dir;
        }
        board[i + 1] = board[i];
    }

    board[0] = (struct ballData) { 0, 0 };
}

void setCursorTopLeft (void)
{
    const COORD coord = { 0, 0 };

    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}    

int main (int argc, char *argv[])
{
    // these are the default values -- use a cmd line argument to change them
    // first cmd line argument = number of balls to drop
    // second cmd line argument = rate (how often a ball is dropped)
    int numberOfBalls = 1024, rate = 1;

    if ( argc > 1 )
        numberOfBalls = atoi (argv[1]);
    if ( argc > 2 )
        rate = atoi (argv[2]);

    if ( rate <= 0 ) {
        printf ("\nProgram halted: the variable 'rate' must be > 0\n");
        return 1;
    }

    // screen length = 41 80-char lines + 1 79-char line
    const int scrLen = 3359;
    char scr[scrLen + 1];
    memset (scr, ' ', scrLen);
    scr[scrLen] = '\0';
    struct ballData board[NUMBEROFROWS] = { { 0, 0 } };
    int bin[NUMBEROFBINS] = { 0 };
    const int milliseconds = 20;
    srand (time(NULL));

    clearScreen ();
    drawInitialScreen (scr, numberOfBalls);
    const int dropCountCol = RTMARGIN - 1 - numberLen (numberOfBalls) - 4;

    int catchCount = 0, cycle = 0, dropCount = 0;

    do {
        checkBallInBin (scr, board[NUMBEROFROWS - 1].col, &catchCount,
                       bin);
        moveBallsDown (board);
        if ( cycle == 0 )
            checkNewBall (scr, dropCountCol, &dropCount, numberOfBalls,
                         board);
        drawOrEraseBalls (scr, board, BALLGRAPHIC);
        displayScreen (scr);
        delay (milliseconds);
        setCursorTopLeft ();
        drawOrEraseBalls (scr, board, SPACEGRAPHIC);

        ++cycle;
        cycle %= rate;
    }
    while ( catchCount < numberOfBalls );

    drawHistogram (scr, bin);
    displayScreen (scr);

    return 0;    
}
\$\endgroup\$
2
\$\begingroup\$

The inclusion of the header file windows.h will cause this to not compile in any non-Windows environment.


From the MAN page for memcpy():

The memcpy() function copies n bytes from memory area src to memory area dest. The memory areas must not overlap. Use memmove(3) if the memory areas do overlap.


The statement system( "cls" ); will cause an error in most environments, other than Windows. I suggest using the ANSI escape sequences.


The statement Sleep (interval); will fail to compile in most environments. Using sleep() (lowercase S) may work, but is discouraged, I suggest using nanosleep().


Regarding const float k = 34 / 100.0;, the 100.0 is a double literal. I suggest: const float k = 34 / 100.0f;.


Regarding SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);, neither SetConsoleCursorPosition() nor GetStdHandle() nor STD_OUTPUT_HANDLE are available outside the Windows environment.


Regarding srand (time(NULL));: this will not work in an environment that does not have a RTC (Real Time Clock).


Regarding const int scrLen = 3359;: this is making an assumption about the terminal layout that (mostly) is not true. I suggest setting a newline '\n' at the 79th column of each row of the scr array.

\$\endgroup\$
  • \$\begingroup\$ Thank you for the comments. I considered using memmove (instead of memcpy), however, there didn't seem to be any possibility of overlap. The suggestion about double and float in the histogram calculation was something I had not considered. Putting a '/n' at the end of each line of output would prevent using the fast puts command which outputs the entire screen at once. I have essentially no experience with Linux -- would using the ncurses library allow the program to run on Windows and Linux boxes? \$\endgroup\$ – ringzero Jul 15 '18 at 21:05
  • \$\begingroup\$ no overlap??? The source and destination completely overlap each other. (offset by 1 byte) \$\endgroup\$ – user3629249 Jul 16 '18 at 0:20
  • \$\begingroup\$ suggest not using ncurses until you have a lot more experience. \$\endgroup\$ – user3629249 Jul 16 '18 at 0:21
  • \$\begingroup\$ Note: embedding newlines in the scr[] array will have no problems for the puts() functionality. \$\endgroup\$ – user3629249 Jul 16 '18 at 0:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.