Skip to content

feat: Allow -C to specify multi-color, custom palette #200

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion cmatrix.1
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Locks cmatrix, Unable to quit
Print usage and exit
.TP
.I "\-r"
"Rainbow" mode, rainbow colored character
"Rainbow" mode, rainbow colored characters. Uses colors from -C if multiple are specified, otherwise uses the full rainbow palette.
.TP
.I "\-k"
Every characters change
Expand All @@ -62,6 +62,9 @@ Screen update delay 0 - 9, default 4
.I "\-C color"
Use this color for matrix (default green).
Valid colors are green, red, blue, white, yellow, cyan, magenta and black.
Multiple colors can be specified by separating them with commas (e.g., "red,blue,green").
When multiple colors are specified, they are used proportionally.
Repeats are allowed for different mixes (e.g. "red,blue,blue" or "red,red,blue")
.TP
.I "\-M message"
Add a message in the center of cmatrix
Expand Down
195 changes: 151 additions & 44 deletions cmatrix.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
typedef struct cmatrix {
int val;
bool is_head;
int color; // For multi-color mode, each character can have a different color
} cmatrix;

/* Global variables */
Expand All @@ -92,6 +93,11 @@ int *updates = NULL; /* What does this do again? */
#ifndef _WIN32
volatile sig_atomic_t signal_status = 0; /* Indicates a caught signal */
#endif
int* color_array = NULL; /* Array of color values for multi-color mode */
int num_colors = 0; /* Number of colors in current use (0 when not in multi-color mode) */
int original_num_colors = 0; /* Remember the original number of colors */
int mcolor = COLOR_GREEN; /* Current matrix color */
int rainbow = 0; /* Flag for rainbow mode */
Comment on lines +99 to +100
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


int va_system(char *str, ...) {

Expand Down Expand Up @@ -164,7 +170,9 @@ void usage(void) {
printf(" -M [message]: Prints your message in the center of the screen. Overrides -L's default message.\n");
printf(" -u delay (0 - 10, default 4): Screen update delay\n");
printf(" -C [color]: Use this color for matrix (default green)\n");
printf(" Multiple colors can be specified as comma-separated list (e.g. -C red,blue,green)\n");
printf(" -r: rainbow mode\n");
printf(" Will use colors from -C if multiple specified, otherwise full rainbow\n");
printf(" -m: lambda mode\n");
printf(" -k: Characters change while scrolling. (Works without -o opt.)\n");
printf(" -t [tty]: Set tty to use\n");
Expand All @@ -189,6 +197,29 @@ void *nmalloc(size_t howmuch) {
return r;
}

/* Parse a color name and return the corresponding COLOR_* constant */
int parse_color(const char* color_name) {
if (!strcasecmp(color_name, "green")) {
return COLOR_GREEN;
} else if (!strcasecmp(color_name, "red")) {
return COLOR_RED;
} else if (!strcasecmp(color_name, "blue")) {
return COLOR_BLUE;
} else if (!strcasecmp(color_name, "white")) {
return COLOR_WHITE;
} else if (!strcasecmp(color_name, "yellow")) {
return COLOR_YELLOW;
} else if (!strcasecmp(color_name, "cyan")) {
return COLOR_CYAN;
} else if (!strcasecmp(color_name, "magenta")) {
return COLOR_MAGENTA;
} else if (!strcasecmp(color_name, "black")) {
return COLOR_BLACK;
} else {
return -1; /* Invalid color */
}
}
Comment on lines +200 to +221
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We needed to parse colors in two places, so this big if/else was pulled out into its own function.


/* Initialize the global variables */
void var_init() {
int i, j;
Expand Down Expand Up @@ -223,6 +254,7 @@ void var_init() {
for (i = 0; i <= LINES; i++) {
for (j = 0; j <= COLS - 1; j += 2) {
matrix[i][j].val = -1;
matrix[i][j].color = mcolor; // Initialize with default color
}
}

Expand Down Expand Up @@ -310,6 +342,27 @@ void resize_screen(void) {
refresh();
}

/**
* Selects a color for a matrix character based on current global program state.
*
* This function uses GLOBAL STATE variables:
* - num_colors: Number of colors in the custom palette
* - color_array: Array of custom color values
* - rainbow: Flag indicating if rainbow mode is active
* - mcolor: Current default color
*
* @return The selected color value (COLOR_* constant)
*/
int select_matrix_color(void) {
if (num_colors > 0 && color_array != NULL && !rainbow) {
/* Multi-color mode - select from custom palette */
return color_array[rand() % num_colors];
} else {
/* Single color mode or no custom palette - use global color */
return mcolor;
}
}
Comment on lines +345 to +364
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DRY

This specific code was repeated a lot; made it its own function to reduce total lines of code & opportunity for error.


int main(int argc, char *argv[]) {
int i, y, z, optchr, keypress;
int j = 0;
Expand All @@ -323,8 +376,6 @@ int main(int argc, char *argv[]) {
int random = 0;
int update = 4;
int highnum = 0;
int mcolor = COLOR_GREEN;
int rainbow = 0;
Comment on lines -326 to -327
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These were hoisted to global:

  • mcolor because we need to initialize a color in var_init(), and it made sense to use "the current default color" to do so - but in order to do so, mcolor couldn't be local to main().
  • rainbow to allow the select_matrix_color function to be global.

int lambda = 0;
int randnum = 0;
int randmin = 0;
Expand Down Expand Up @@ -356,26 +407,48 @@ int main(int argc, char *argv[]) {
bold = 2;
break;
case 'C':
if (!strcasecmp(optarg, "green")) {
mcolor = COLOR_GREEN;
} else if (!strcasecmp(optarg, "red")) {
mcolor = COLOR_RED;
} else if (!strcasecmp(optarg, "blue")) {
mcolor = COLOR_BLUE;
} else if (!strcasecmp(optarg, "white")) {
mcolor = COLOR_WHITE;
} else if (!strcasecmp(optarg, "yellow")) {
mcolor = COLOR_YELLOW;
} else if (!strcasecmp(optarg, "cyan")) {
mcolor = COLOR_CYAN;
} else if (!strcasecmp(optarg, "magenta")) {
mcolor = COLOR_MAGENTA;
} else if (!strcasecmp(optarg, "black")) {
mcolor = COLOR_BLACK;
if (strchr(optarg, ',')) {
/* Multiple colors specified, parse them */
char *colors_copy = strdup(optarg);
if (!colors_copy) {
c_die("CMatrix: Out of memory!\n");
}

/* Count number of colors */
int color_count = 1;
for (char *p = colors_copy; *p; p++) {
if (*p == ',') color_count++;
}

/* Allocate the color array */
color_array = nmalloc(color_count * sizeof(int));
num_colors = 0;

/* Parse each color */
char *token = strtok(colors_copy, ",");
while (token != NULL) {
int color = parse_color(token);
if (color == -1) {
free(colors_copy);
c_die(" Invalid color selection '%s'\n Valid "
"colors are green, red, blue, "
"white, yellow, cyan, magenta and black.\n", token);
}
color_array[num_colors++] = color;
token = strtok(NULL, ",");
Comment on lines +427 to +438
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multi-Color - Init

We don't deduplicate the colors when parsing, so -C red,red,blue will result in 2x as much red as blue. This is on purpose to allow more nuanced color mixes.

}
original_num_colors = num_colors; // Remember the original count
free(colors_copy);
} else {
c_die(" Invalid color selection\n Valid "
"colors are green, red, blue, "
"white, yellow, cyan, magenta " "and black.\n");
/* Single color specified */
num_colors = 0;
color_array = NULL; /* Ensure color_array is NULL */
mcolor = parse_color(optarg);
if (mcolor == -1) {
c_die(" Invalid color selection\n Valid "
"colors are green, red, blue, "
"white, yellow, cyan, magenta and black.\n");
}
}
break;
case 'c':
Expand Down Expand Up @@ -618,42 +691,57 @@ if (console) {
case '!':
mcolor = COLOR_RED;
rainbow = 0;
num_colors = 0;
break;
case '@':
mcolor = COLOR_GREEN;
rainbow = 0;
num_colors = 0;
break;
case '#':
mcolor = COLOR_YELLOW;
rainbow = 0;
num_colors = 0;
break;
case '$':
mcolor = COLOR_BLUE;
rainbow = 0;
num_colors = 0;
break;
case '%':
mcolor = COLOR_MAGENTA;
rainbow = 0;
num_colors = 0;
break;
case 'r':
rainbow = 1;
if (original_num_colors > 0) {
num_colors = original_num_colors; // Restore original count for rainbow
}
break;
case 'm':
lambda = !lambda;
break;
case '^':
mcolor = COLOR_CYAN;
rainbow = 0;
num_colors = 0;
break;
case '&':
mcolor = COLOR_WHITE;
rainbow = 0;
num_colors = 0;
break;
case 'p':
case 'P':
pause = (pause == 0)?1:0;
break;

case '+':
rainbow = 0;
if (original_num_colors > 0) {
num_colors = original_num_colors; // Restore multi-color mode
}
break;
Comment on lines +739 to +744
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shift + + will switch into non-rainbow multi-color mode.

}
}
}
Expand All @@ -664,6 +752,7 @@ if (console) {
if (oldstyle) {
for (i = LINES - 1; i >= 1; i--) {
matrix[i][j].val = matrix[i - 1][j].val;
matrix[i][j].color = matrix[i - 1][j].color;
}
random = (int) rand() % (randnum + 8) + randmin;

Expand All @@ -683,13 +772,15 @@ if (console) {
matrix[0][j].val = 0;
} else {
matrix[0][j].val = (int) rand() % randnum + randmin;
matrix[0][j].color = select_matrix_color();
}
spaces[j] = (int) rand() % LINES + 1;
}
} else if (random > highnum && matrix[1][j].val != 1) {
matrix[0][j].val = ' ';
} else {
matrix[0][j].val = (int) rand() % randnum + randmin;
matrix[0][j].color = select_matrix_color();
}

} else { /* New style scrolling (default) */
Expand All @@ -700,6 +791,10 @@ if (console) {
&& matrix[1][j].val == ' ') {
length[j] = (int) rand() % (LINES - 3) + 3;
matrix[0][j].val = (int) rand() % randnum + randmin;
matrix[0][j].is_head = true;

// Assign a random color to the character head
matrix[0][j].color = select_matrix_color();

spaces[j] = (int) rand() % LINES + 1;
}
Expand Down Expand Up @@ -740,6 +835,9 @@ if (console) {
matrix[i][j].val = (int) rand() % randnum + randmin;
matrix[i][j].is_head = true;

// Assign a random color to the new character head
matrix[i][j].color = select_matrix_color();

/* If we're at the top of the column and it's reached its
full length (about to start moving down), we do this
to get it moving. This is also how we keep segments not
Expand Down Expand Up @@ -794,28 +892,37 @@ if (console) {
}
} else {
if (rainbow) {
int randomColor = rand() % 6;

switch (randomColor) {
case 0:
mcolor = COLOR_GREEN;
break;
case 1:
mcolor = COLOR_BLUE;
break;
case 2:
mcolor = COLOR_BLACK;
break;
case 3:
mcolor = COLOR_YELLOW;
break;
case 4:
mcolor = COLOR_CYAN;
break;
case 5:
mcolor = COLOR_MAGENTA;
break;
}
if (num_colors > 0 && color_array != NULL) {
// Rainbow mode with custom color palette
mcolor = color_array[rand() % num_colors];
Comment on lines +895 to +897
Copy link
Author

@Texarkanine Texarkanine May 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rainbow Mode - Render

When in rainbow mode AND there's a custom color palette, colors are randomly picked from that palette instead of the default 6 colors.

} else {
// Standard rainbow mode with predefined colors
int randomColor = rand() % 6;

switch (randomColor) {
case 0:
mcolor = COLOR_GREEN;
break;
case 1:
mcolor = COLOR_BLUE;
break;
case 2:
mcolor = COLOR_BLACK;
break;
case 3:
mcolor = COLOR_YELLOW;
break;
case 4:
mcolor = COLOR_CYAN;
break;
case 5:
mcolor = COLOR_MAGENTA;
break;
}
}
} else if (num_colors > 0) {
// Multi-color mode - use stored character colors
mcolor = matrix[i][j].color;
Comment on lines +923 to +925
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multi-Color Mode - Render

A color was assigned to the character when it entered the matrix; just use it.

}
attron(COLOR_PAIR(mcolor));
if (matrix[i][j].val == 1) {
Expand Down