Cetris

Posted on Feb 23, 2026

Screenshot from Cetris

The Project

This Cetris project (Tetris in C) is an old project of mine, from a time when I was learning how to use make for the first time, and compile a C project that is composed of multiple .h and .c files. I got introduced to many interesting problems while working on this project. And bear in mind, that was a time when LLMs hadn’t been invented yet. I remember looking the net for Tetris versions, and trying to decide which rotation logic should I use, and which pieces should be on the board. From the project README.md:

“Piece rotation follows the Super Rotation System (SRS), and pieces are drawn from a 7-bag randomiser that guarantees every piece type appears once before any repeat.”

The Color Problem

One of the main obstacles I faced was colors, that’s why I specifically specified that THIS TETRIS CLONE HAS COLORS in the project README.md. Now you may say, okay, your clone has colors, but basically all terminal Tetris clones have colors. It is not something special. Surely, that is right. You are just going to use ANSI escape sequences, and display the pieces in the color you want, correct? Not actually. You see, not all terminals support 8-bit colors, and the terminal that I was working on at the time only supported 8 different colors. I could have chosen a terminal that supported more colors, but for some reason, I didn’t, and tried to find a way to fit more colors in the 8 colors range. Now you might be thinking, that the original Tetris had only 7 tetrominoes, and 8 colors was enough. But after removing white (for borders) and black (for background), I was only left with 6 colors, not enough to make a fully colored Tetris clone.

So, I started experimenting with the ANSI codes. Tried different combinations, styles, but actually I was not that optimistic about it. You see, the color limit seemed like a hard limit, and I was running out of things to try. I was truly thinking of installing a terminal that supports more colors, or instead even turning the whole thing monochrome.

The Solution

Then, I found it: The ANSI code (or option, specifically) that really let me double the color space! You see, when you bold a character, it actually changes color a little bit. If it was red, it gets a brighter, more intense shade. But my tetrominoes consisted of background-colored blocks, and as far as I can tell, you were not able to bold backgrounds directly.

However, there is a neat trick: you can reverse the foreground and background colors using a single ANSI parameter (n=7 for reverse, n=1 for bold). By swapping foreground and background, the block’s visual color now comes from the foreground, which can be bolded. This means the same base color (say, COLOR_YELLOW) could render as two perceptually distinct shades: normal yellow (Orange) and bold+reversed yellow (a brighter Yellow). Suddenly, 8 color limit became 16 effective colors, enough to color all 7 tetrominoes plus the board border!

To visualise the trick, here is a small standalone program that prints all 8 effective colors in an 8×2 grid: The first row shows the plain bg variant, and the second row shows the bold+reverse variant of the same base color. Install libncurses-dev and compile with -lncurses flag:

#include <ncurses.h>

// Pair index, base color, 
// bold+reverse flag
static const struct {
    int       pair;
    int       fg;
    int       bold_reverse;
} COLORS_8x2[16] = {
    /* row 0: plain bg */
    { 1, COLOR_CYAN,    0 },
    { 2, COLOR_BLUE,    0 },
    { 3, COLOR_YELLOW,  0 }, 
    { 4, COLOR_GREEN,   0 },
    { 5, COLOR_MAGENTA, 0 },
    { 6, COLOR_RED,     0 },
    { 7, COLOR_WHITE,   0 },
    { 8, COLOR_BLACK,   0 },
    /* row 1: bold+reverse */
    { 1, COLOR_CYAN,    1 },
    { 2, COLOR_BLUE,    1 },
    { 3, COLOR_YELLOW,  1 }, 
    { 4, COLOR_GREEN,   1 },
    { 5, COLOR_MAGENTA, 1 },
    { 6, COLOR_RED,     1 },
    { 7, COLOR_WHITE,   1 },
    { 8, COLOR_BLACK,   1 },
};

int main(void)
{
    initscr();
    start_color();
    cbreak();
    noecho();

    // Initialise all 8 base pairs (same fg and bg)
    init_pair(1, COLOR_CYAN,    COLOR_CYAN);
    init_pair(2, COLOR_BLUE,    COLOR_BLUE);
    init_pair(3, COLOR_YELLOW,  COLOR_YELLOW);
    init_pair(4, COLOR_GREEN,   COLOR_GREEN);
    init_pair(5, COLOR_MAGENTA, COLOR_MAGENTA);
    init_pair(6, COLOR_RED,     COLOR_RED);
    init_pair(7, COLOR_WHITE,   COLOR_WHITE);
    init_pair(8, COLOR_BLACK,   COLOR_BLACK);

    for (int i = 0; i < 16; i++) {
        int col = (i % 8) * 4;
        int row = (i / 8) * 2;  

        if (COLORS_8x2[i].bold_reverse)
            attron(A_BOLD | A_REVERSE);

        attron(COLOR_PAIR(COLORS_8x2[i].pair));

        // Print a 4-wide, 2-tall solid block.
        mvprintw(row,     col, "    ");
        mvprintw(row + 1, col, "    ");

        attroff(COLOR_PAIR(COLORS_8x2[i].pair));
        attroff(A_BOLD | A_REVERSE);
    }

    mvprintw(5, 0, "Press any key to exit...");
    refresh();
    getch();
    endwin();
    return 0;
}

Color matrix

Conclusion

If I were to redo this project today, I would write proper unit tests for the rotation and collision logic, and probably reach for a terminal that supports 256 colors from the start. But then again, I would never have found that bold+reverse trick.

The source is on GitLab if you want to poke around. And always, thanks for reading.