ccurses

Blank column when offset reaches end of map


People, I have a matrix that moves from left to right and only the characters of the array that are inside the circle appear (distance = sqrt(pow((x - CENTER_X) / ASPECT_RATIO, 2) + pow(y - CENTER_Y, 2)) ;), when the 'mapWorld' matrix reaches the end, the frame starts again, but with two blank columns, and some characters that I can't understand.
I thought that when the offset reaches the end of the map, the value of x was negative in the mapWorld[y][x - offset] expression. causing an invalid index in the mapWorld array to be accessed, resulting in blank characters.
Then I then added a check to ensure that x - offset is a non-negative value.

if (mapWorld[y][(x - offset + MAP_WIDTH) % MAP_WIDTH] == '-') {
    // ...
} else if (mapWorld[y][(x - offset + MAP_WIDTH) % MAP_WIDTH] == '+') {
    // ...
}

It has improved a lot but there is still a blank column.
I would like to better clarify the words but I don't really know what could be happening. Please see the GIF.

#include <stdio.h>
#include <math.h>
#include <curses.h>

#define MAP_HEIGHT 34
#define MAP_WIDTH 141
#define ASPECT_RATIO 2
#define RADIUS 20
#define CENTER_X (MAP_WIDTH / 2.0)
#define CENTER_Y (MAP_HEIGHT / 2.0)

const char *mapWorld [MAP_HEIGHT] = {
      // Map omitted for brevity
};

int clip(int v, int max) {
    return v < max ? v : max - 1;
}

int main() {
    initscr(); 
    start_color(); 
    cbreak();
    noecho();
    curs_set(0);

    init_pair(1, COLOR_BLUE, COLOR_BLACK);
    init_pair(2, COLOR_GREEN, COLOR_BLACK);

    while (1) {
        //Right to left shift loop
        for (int offset = 0; offset < MAP_WIDTH; offset++) {
            erase();

            //Loop through all array coordinates
            for (int y = 0; y < MAP_HEIGHT; y++) {
                for (int x = 0; x < MAP_WIDTH; x++) {
                    float distance = sqrt(pow((x - CENTER_X) / ASPECT_RATIO, 2) + pow(y - CENTER_Y, 2));

                    //Checks if the point is inside the circle
                    if (distance <= RADIUS) {
                        //Defines the color for the characters '-' and '+'
                        if (mapWorld[y][x - offset] == '-') {
                            attron(COLOR_PAIR(1));
                        } else if (mapWorld[y][x - offset] == '+') {
                            attron(COLOR_PAIR(2));
                        }

                        //Print the corresponding character
                        mvprintw(y, x, "%c", mapWorld[y][x - offset]);

                        //Disable color
                        attroff(COLOR_PAIR(1));
                        attroff(COLOR_PAIR(2));
                    }
                }
            }
            refresh();
            napms(50);
        }
    }
    endwin();
    return 0;
}

Map

const char * mapWorld[MAP_HEIGHT] = {
        "--------------------------------------------------------------------------------------------------------------------------------------------",
        "--------------------------------------------------------------------------------------------------------------------------------------------",
        "----------------------------------+++++++--+++++++++++++++++++------------------------------------------------------------------------------",
        "----------------------+-+++++--+-+-+++++--------++++++++++++++-----------------------------+----------++++++++++++++--+-----++--------------",
        "------++++++++++++++++++++++++++++++++--++++-----++++++++++-----------------++++++++-----+++++++++++++++++++++++++++++++++++++++++++++++++++",
        "------+++++++++++++++++++++++++++++-----++++------++++-------------------++++-+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-",
        "--------++-------+++++++++++++++++------++++++----------------------+----++++--++++++++++++++++++++++++++++++++++++++++++++++------++-------",
        "--------------------++++++++++++++++++-+++++++++--------------------++--++++++++++++++++++++++++++++++++++++++++++++++++++++-------+--------",
        "----------------------+++++++++++++++++++++++------------------------+++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------",
        "----------------------+++++++++++++++++++++------------------------++++---+-+++++----++++-+++++++++++++++++++++++++++++++-------------------",
        "----------------------+++++++++++++++++++--------------------------+++--------+--++++++++--+++++++++++++++++++++++++---+----+---------------",
        "-------------------------++++++++++++++----------------------------+++++++++--+----++++++++++++++++++++++++++++++++++-----------------------",
        "--------------------------++++++------+--------------------------++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------",
        "-----------------------------+++--------------------------------++++++++++++++++++++-++++++++----+++++++++++++++++--------------------------",
        "-------------------------------++-++----------------------------+++++++++++++++++++++-+++++-------++++-----+++++----------------------------",
        "------------------------------------++--------------------------+++++++++++++++++++++++++----------++-------+-++----------------------------",
        "----------------------------------------+++++++------------------++++++++++++++++++++++++---------------------------------------------------",
        "---------------------------------------+++++++++++------------------------++++++++++++++---------------------+---+++------------------------",
        "---------------------------------------++++++++++++++++-------------------++++++++++++-----------------------++--++--------++---------------",
        "---------------------------------------+++++++++++++++++-------------------++++++++++---------------------------------------++--------------",
        "----------------------------------------+++++++++++++++--------------------+++++++++++----------------------------------+++--+--------------",
        "------------------------------------------+++++++++++++--------------------+++++++++---++----------------------------++++++++++-------------",
        "-------------------------------------------+++++++++------------------------++++++++---+--------------------------+++++++++++++++-----------",
        "------------------------------------------+++++++++-------------------------++++++--------------------------------+++++++++++++++-----------",
        "------------------------------------------+++++++----------------------------+++-----------------------------------+++----+++++++-----------",
        "------------------------------------------++++--------------------------------------------------------------------------------+-------------",
        "-----------------------------------------++++-----------------------------------------------------------------------------------------------",
        "-----------------------------------------+++------------------------------------------------------------------------------------------------",
        "--------------------------------------------------------------------------------------------------------------------------------------------",
        "--------------------------------------------------------------------------------------------------------------------------------------------",
        "--------------------------------------------------------------------------------------------------------------------------------------------",
        "--------------------------------------------++-------------------------------------+-++++++++++++--+++++++++++++++++++++++++++++++++--------",
        "--------------------+++++++---++++++++++++++++-----------------+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------",
        "----------+++++++++++++++++++++++++++++++----------+----+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------"
};

Solution

  • Adding a little bit of debug to this

    if (distance <= RADIUS) {
        if (x - offset < 0)
            fprintf(stderr, "d%f o%d y%d x%d c%d\n", distance, offset, y, x, x - offset);
    /* ... */
    

    and compiling () with -fsanitize=address produces the following diagnostics:

    d19.976549 o32 y14 x31 c-1
    =================================================================
    ==951==ERROR: AddressSanitizer: global-buffer-overflow on address 0x0001083e123f at pc 0x0001083e0382 bp 0x7ffee7821950 sp 0x7ffee7821948
    READ of size 1 at 0x0001083e123f thread T0
        #0 0x1083e0381 in main+0x2a1 (a.out:x86_64+0x100002381)
        #1 0x7fff73177cc8 in start+0x0 (libdyld.dylib:x86_64+0x1acc8)
    
    0x0001083e123f is located 1 bytes to the left of global variable '<string literal>' defined in 'vis.c:27:9' (0x1083e1240) of size 141
      '<string literal>' is ascii string '-------------------------------++-++----------------------------+++++++++++++++++++++-+++++-------++++-----+++++----------------------------'
    0x0001083e123f is located 50 bytes to the right of global variable '<string literal>' defined in 'vis.c:26:9' (0x1083e1180) of size 141
      '<string literal>' is ascii string '-----------------------------+++--------------------------------++++++++++++++++++++-++++++++----+++++++++++++++++--------------------------'
    SUMMARY: AddressSanitizer: global-buffer-overflow (a.out:x86_64+0x100002381) in main+0x2a1
    Shadow bytes around the buggy address:
      0x10002107c1f0: 00 00 00 00 00 00 00 00 00 05 f9 f9 f9 f9 f9 f9
      0x10002107c200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
      0x10002107c210: 00 05 f9 f9 f9 f9 f9 f9 00 00 00 00 00 00 00 00
      0x10002107c220: 00 00 00 00 00 00 00 00 00 05 f9 f9 f9 f9 f9 f9
      0x10002107c230: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    =>0x10002107c240: 00 05 f9 f9 f9 f9 f9[f9]00 00 00 00 00 00 00 00
      0x10002107c250: 00 00 00 00 00 00 00 00 00 05 f9 f9 f9 f9 f9 f9
      0x10002107c260: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
      0x10002107c270: 00 05 f9 f9 f9 f9 f9 f9 00 00 00 00 00 00 00 00
      0x10002107c280: 00 00 00 00 00 00 00 00 00 05 f9 f9 f9 f9 f9 f9
      0x10002107c290: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    Shadow byte legend (one shadow byte represents 8 application bytes):
      Addressable:           00
      Partially addressable: 01 02 03 04 05 06 07 
      Heap left redzone:       fa
      Freed heap region:       fd
      Stack left redzone:      f1
      Stack mid redzone:       f2
      Stack right redzone:     f3
      Stack after return:      f5
      Stack use after scope:   f8
      Global redzone:          f9
      Global init order:       f6
      Poisoned by user:        f7
      Container overflow:      fc
      Array cookie:            ac
      Intra object redzone:    bb
      ASan internal:           fe
      Left alloca redzone:     ca
      Right alloca redzone:    cb
      Shadow gap:              cc
    ==951==ABORTING
    

    From this we can see that as distance approaches RADIUS, a negative index is produced by x - offset, resulting in mapWorld[14][-1], and subsequently Undefined Behaviour occurs.

    As you have seen, you must avoid creating a negative index, and your solution of

    mapWorld[y][(x - offset + MAP_WIDTH) % MAP_WIDTH]
    

    seemingly works well.

    The last thing to note is your strings have a size of 141, but a length of 140. You index the null-terminating byte creating a blank column. Change

    #define MAP_WIDTH 141
    

    to

    #define MAP_WIDTH 140