Search code examples
cncurses

Why is the colored ribbon repeated multiple times in the output


The code in main prints a colored ribbon before the while and when I try to repeat this process inside the while after the user enters a 1, the constant adjusts the possible wavelengths (that are four) within the visible and assigns a character to the specified position of the array based on the o shape's wavelength, otherwise prints empty space for other colors.

Result:

It happens that after the user informs 1, the tape is repeated 3 more times after the first one, as in the print of the result above, only a ribbon should be printed. What makes it happen? compile -lncursesw environment xfce4-terminal
I think it's because of the wprintw(stdscr, "%lc", randomCharacter);, I noticed that each new strip contains the randomCharacter drawn in a different position corresponding to one of the four possible lengths of the adjustedConstant, so this totals 4 strips instead of one.
i == index it is a way of visualizing the colors associated with specific wavelengths within the visible light spectrum.
When the user enters a value other than -1, the program calculates the wavelength associated with the given input using the formula given by adjustedConstant. If that wavelength is within the visible range (between 400 nm and 700 nm), the program determines the corresponding position in the array of colors (colors) and prints a random character with the color associated with that wavelength.
When pressed 1 ->

1n = 2, 2n = 3, Wavelength: 6.564696e-07 meters
1n = 2, 2n = 4, Wavelength: 4.862738e-07 meters
1n=2, 2n=5, Wavelength: 4.341730e-07 meters
1n=2, 2n=6, Wavelength: 4.102935e-07 meters
#include <stdio.h>
#include <stdlib.h>
#include <curses.h>
#include <locale.h>
#include <math.h>

typedef struct {
  int r;
  int g;
  int b;
} RGB;

RGB newRGB(int r, int g, int b) {
  RGB color;
  color.r = r;
  color.g = g;
  color.b = b;
  return color;
}

void calculateColor(RGB *colors, int totalColors, int currentIndex, float saturation, float luminosity);
double adjustedConstant() {
  return 1.0973731568508E7 / (1 + 9.10938356E-31 / 1.67262192E-27);
}

int main() {
  setlocale(LC_ALL, "");

  int totalColors = 60;
  RGB colors[totalColors];
  float saturation = 1.0;
  float luminosity = 0.3;

  initscr();
  start_color();

  int end = totalColors - 18;
  int start = end - 42;
  for (int i = end; i >= start; i--) {
    calculateColor(&colors[i], totalColors, i, saturation, luminosity);

    // Convert RGB colors to curses default model
    int r = colors[i].r * 1000 / 255;
    int g = colors[i].g * 1000 / 255;
    int b = colors[i].b * 1000 / 255;

    // Set color in curses
    init_color(i + 16, r, g, b); // needs to define the pair of colors i+16
    init_pair(i + 16, COLOR_BLACK, i + 16);

    // Print the color
    attron(COLOR_PAIR(i + 16)); // needs to activate the pair of colors i+16
    printw("  ");
    attroff(COLOR_PAIR(i + 16));
  }

  // Vector of characters for drawing
  int characters[] = {
    9474, 9475, 10072, 10073, 12321
  };
  int numCharacters = sizeof(characters) / sizeof(characters[0]);

  // Loop to print the colors on the screen
  int wavelength;
  while (1) {
    int number;
    printw("\n\nEnter 1 or -1 to exit: ");
    scanw("%d", &number);

    if (number == -1) {
      break; // Exit the loop if the user enters -1
    }

    // Wavelength
    int n1 = 1, n2 = 2;
    double lambda_a = 0;

    while (1) {
      lambda_a = 1.0 / (adjustedConstant() * ((1.0 / (n1 * n1)) - (1.0 / (n2 * n2))));

      if (lambda_a >= 400E-9 && lambda_a <= 700E-9) {

        // Calculate the position in the colors array based on the wavelength
        int index = (700 - lambda_a * 1e9) * (totalColors - 10.5) / (750 - 380);
        // Print all colors
        for (int i = end; i >= start; i--) {
          if (i == index) {
            // Activate the color pair for the corresponding wavelength color
            attron(COLOR_PAIR(i + 16));
            // Draw one of the characters randomly
            int randomCharacter = characters[rand() % numCharacters];
            wprintw(stdscr, "%lc", randomCharacter);
            // Deactivate the color pair
            attroff(COLOR_PAIR(i + 16));
          } else {
            // Draw empty spaces for the other colors
            attron(COLOR_PAIR(i + 16));
            printw("  ");
            attroff(COLOR_PAIR(i + 16));
          }
        }
        refresh(); // Update the screen
      }
      if (lambda_a > 700E-9) {
        break; // Wavelength exceeded the visible range, end the loop
      }
      n2++; // Increment the value of 2n
      if (n2 <= 6) {
        continue;
      }
      n1++; // Increment the value of 1n and reset 2n to a value greater than 1n
      n2 = n1 + 1;
    }
  }
  getch(); // Wait for user input
  endwin(); // End curses mode
  return 0;
}

void calculateColor(RGB *colors, int totalColors, int currentIndex, float saturation, float luminosity) {
  float hue = currentIndex * (360.0 / totalColors);
  float chroma = (1 - fabs(2 * luminosity - 1)) * saturation;
  float x = chroma * (1 - fabs(fmod(hue / 60, 2) - 1));
  float m = luminosity - chroma / 2;

  float r, g, b;
  if (hue >= 0 && hue < 60) {
    r = chroma;
    g = x;
    b = 0;
  } else if (hue >= 60 && hue < 120) {
    r = x;
    g = chroma;
    b = 0;
  } else if (hue >= 120 && hue < 180) {
    r = 0;
    g = chroma;
    b = x;
  } else if (hue >= 180 && hue < 240) {
    r = 0;
    g = x;
    b = chroma;
  } else if (hue >= 240 && hue < 300) {
    r = x;
    g = 0;
    b = chroma;
  } else {
    r = chroma;
    g = 0;
    b = x;
  }
  colors->r = (r + m) * 255;
  colors->g = (g + m) * 255;
  colors->b = (b + m) * 255;
}

Solution

  • The loop logic in the posted code is incorrect.

    OP code has an outer loop that calculates lambda_a values which presumably correspond with spectral absorption lines, and an inner loop that prints a color spectrum, either as blocks or as characters depending on the color index associated with the lambda_a value. The inner loop prints a color spectrum and checks for a match with the index associated with lambda_a, printing a character in that case, and then lambda_a is modified in the outer loop. By this process, four values of lambda_a appear in the visible spectrum range (lambda_a >= 400E-9 && lambda_a <= 700E-9) so the spectrum is printed four times.

    A better approach would be to calculate and cache color indexes which match an absorption line. Then when the color spectrum is printed once, if the color index matches a cached index a character is printed.

    Here is a modified version of OP code that implements this. There is now a loop which populates line_idxs with color indexes for absorption lines based on original OP code. A loop which prints a spectrum follows. Note that this code steps through line_idxs in reverse because the printing order of colors is from highest index to lowest while the absorption lines were found in lowest to highest order.

    #include <stdio.h>
    #include <stdlib.h>
    #include <curses.h>
    #include <locale.h>
    #include <math.h>
    
    typedef struct {
        int r;
        int g;
        int b;
    } RGB;
    
    RGB newRGB(int r, int g, int b) {
        RGB color;
        color.r = r;
        color.g = g;
        color.b = b;
        return color;
    }
    
    void calculateColor(RGB *colors, int totalColors, int currentIndex, float saturation, float luminosity);
    double adjustedConstant() {
        return 1.0973731568508E7 / (1 + 9.10938356E-31 / 1.67262192E-27);
    }
    
    int main(void) {
        setlocale(LC_ALL, "");
    
        int totalColors = 60;
        RGB colors[totalColors];
        float saturation = 1.0;
        float luminosity = 0.3;
    
        initscr();
        start_color();
    
        int end = totalColors - 18;
        int start = end - 42;
        for (int i = end; i >= start; i--) {
            calculateColor(&colors[i], totalColors, i, saturation, luminosity);
    
            // Convert RGB colors to curses default model
            int r = colors[i].r * 1000 / 255;
            int g = colors[i].g * 1000 / 255;
            int b = colors[i].b * 1000 / 255;
    
            // Set color in curses
            init_color(i + 16, r, g, b); // needs to define the pair of colors i+16
            init_pair(i + 16, COLOR_BLACK, i + 16);
    
            // Print the color
            attron(COLOR_PAIR(i + 16)); // needs to activate the pair of colors i+16
            printw("  ");
            attroff(COLOR_PAIR(i + 16));
        }
    
        // Vector of characters for drawing
        int characters[] = {
            9474, 12321
        };
        int numCharacters = sizeof(characters) / sizeof(characters[0]);
    
        // Loop to print the colors on the screen
        // int wavelength;
        while (1) {
            int number;
            printw("\n\nEnter 1 or -1 to exit: ");
            scanw("%d", &number);
    
            if (number == -1) {
                break; // Exit the loop if the user enters -1
            }
    
            // Wavelength
            int n1 = 1, n2 = 2;
            int line_idxs[4] = { 0 };  // Save spectral line indices.
    
            // Populate `line_idxs[]` with spectral lines.
            for(size_t i = 0; i < 4;) {
                double lambda_a = 0;
                lambda_a =(1.0 / (adjustedConstant() * ((1.0 / (n1 * n1)) - (1.0 / (n2 * n2)))));
                if (lambda_a >= 400E-9 && lambda_a <= 700E-9) {
                    line_idxs[i] = start + (700 - lambda_a * 1e9) * (totalColors - 10.5) / (750 - 380);
                    i++;
                }
                n2++;
                if (n2 <= 6) continue;
                n1++;
                n2 = n1 + 1;
            }
    
            // Print spectrum.
            for (int i = end, line_idx = 3; i >= start; i--) {
                attron(COLOR_PAIR(i + 16));
                if (line_idx >= 0 && i == line_idxs[line_idx]) {
                    // Print a random character.
                    int randomCharacter = characters[rand() % numCharacters];
                    wprintw(stdscr, "%lc", randomCharacter);
                    --line_idx;
                } else {
                    printw("  ");
                }
                attroff(COLOR_PAIR(i + 16));
            }
            refresh(); // Update the screen
        }
        getch(); // Wait for user input
        endwin(); // End curses mode
    
        return 0;
    }
    
    void calculateColor(RGB *colors, int totalColors, int currentIndex, float saturation, float luminosity) {
        float hue = currentIndex * (360.0 / totalColors);
        float chroma = (1 - fabs(2 * luminosity - 1)) * saturation;
        float x = chroma * (1 - fabs(fmod(hue / 60, 2) - 1));
        float m = luminosity - chroma / 2;
    
        float r, g, b;
        if (hue >= 0 && hue < 60) {
            r = chroma;
            g = x;
            b = 0;
        } else if (hue >= 60 && hue < 120) {
            r = x;
            g = chroma;
            b = 0;
        } else if (hue >= 120 && hue < 180) {
            r = 0;
            g = chroma;
            b = x;
        } else if (hue >= 180 && hue < 240) {
            r = 0;
            g = x;
            b = chroma;
        } else if (hue >= 240 && hue < 300) {
            r = x;
            g = 0;
            b = chroma;
        } else {
            r = chroma;
            g = 0;
            b = x;
        }
        colors->r = (r + m) * 255;
        colors->g = (g + m) * 255;
        colors->b = (b + m) * 255;
    }
    

    Here is the resulting display:

    enter image description here

    Note that the length of the second color spectrum is shorter than the first and the color blocks in the second spectrum are not aligned with the color blocks in the first. This is because the printed representation of the taller bar is narrower than the printed representation of the shorter bar. You can fix this by switching on randomCharacter and printing an extra space for narrower characters. Here I have added some #defines to make working with character codes easier. The switch statement is used so that further characters may be easily introduced by adding additional cases:

    /* ... */
    
    #define TALLBAR   9474
    #define SHORTBAR  12321
    
    /* ... */
    
        int characters[] = { TALLBAR, SHORTBAR };
    
    /* ... */
    
            // Print spectrum.
            for (int i = end, line_idx = 3; i >= start; i--) {
                attron(COLOR_PAIR(i + 16));
                if (line_idx >= 0 && i == line_idxs[line_idx]) {
                    // Print a random character.
                    int randomCharacter = characters[rand() % numCharacters];
                    switch (randomCharacter) {
                    case TALLBAR:
                        wprintw(stdscr, "%lc ", randomCharacter);
                        break;
                    default:
                        wprintw(stdscr, "%lc", randomCharacter);
                    }
                    --line_idx;
                } else {
                    printw("  ");
                }
                attroff(COLOR_PAIR(i + 16));
            }
    
    /* ... */
    

    Of course, many further refinements are possible. Here is the resulting display after printing several spectra using the modified program:

    enter image description here