Skip to content

Corg-Labs/tiles

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 

Repository files navigation

2048

2048 sliding-tile game in the terminal, in C.

A small, self-contained demo written in pure C — no external libraries, just the standard library and POSIX. Part of the Corg-Labs collection of single-file C programs.


How It Works

  1. Tiles slide and merge equal values toward the pressed direction
  2. Each move spawns a new 2 (or occasionally a 4)
  3. Merges add their value to the score
  4. Rows/columns are transposed to reuse one slide routine

Tutorial

This section walks through every moving part of tiles.c so you can understand (or extend) the implementation from top to bottom.

1. The Board and Global State

The entire game board is a single 4×4 array of int, with a separate int for the running score. Both are file-scope globals so every function can reach them without parameter threading:

static int g[4][4], score;

A cell value of 0 means the cell is empty. All non-zero values are powers of two (2, 4, 8 … 2048).

2. Raw Terminal Mode

The game needs to read individual keystrokes without waiting for Enter and without echoing characters to the screen. This is done by switching the terminal to raw mode at startup and restoring it on exit:

static struct termios orig;
static void raw_on(void){
    tcgetattr(0, &orig);
    struct termios t = orig;
    t.c_lflag &= ~(ICANON | ECHO);
    tcsetattr(0, TCSANOW, &t);
    fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
}
static void raw_off(void){ tcsetattr(0, TCSANOW, &orig); printf("\033[?25h"); }

O_NONBLOCK makes read() return immediately with −1 when no key is buffered, so the game loop can call usleep(10000) and poll again rather than blocking.

3. Spawning a New Tile

After every successful move (and at the start of the game), one empty cell is chosen at random and filled with 2 (90 % chance) or 4 (10 % chance):

static void spawn(void){
    int e[16], n = 0;
    for(int i = 0; i < 16; i++) if(!g[i/4][i%4]) e[n++] = i;
    if(!n) return;
    int p = e[rand() % n];
    g[p/4][p%4] = (rand() % 10 == 0) ? 4 : 2;
}

The flat index i is decoded back to (row, col) via i/4 and i%4.

4. The Slide-and-Merge Routine

slide() operates on a single int r[4] row passed by pointer. It performs three steps in sequence:

  1. Compact — pack all non-zero values to the front of a scratch array.
  2. Merge — scan left-to-right; when two adjacent equal values are found, double the left one, add it to score, and zero the right one.
  3. Re-compact — pack the merged result into the output row.
static int slide(int *r){
    int moved = 0, tmp[4] = {0,0,0,0}, k = 0;
    for(int i = 0; i < 4; i++) if(r[i]) tmp[k++] = r[i];
    for(int i = 0; i < 3; i++)
        if(tmp[i] && tmp[i] == tmp[i+1]){ tmp[i] *= 2; score += tmp[i]; tmp[i+1] = 0; }
    int out[4] = {0,0,0,0}; k = 0;
    for(int i = 0; i < 4; i++) if(tmp[i]) out[k++] = tmp[i];
    for(int i = 0; i < 4; i++){ if(r[i] != out[i]) moved = 1; r[i] = out[i]; }
    return moved;
}

The function returns 1 if any cell changed, so the caller knows whether to spawn a new tile.

5. Direction Mapping via Transposition

Rather than writing four separate slide routines, move() remaps each of the four directions to a virtual row and calls the same slide():

static int move(int dir){ /* 0=left 1=right 2=up 3=down */
    int moved = 0;
    for(int i = 0; i < 4; i++){
        int r[4];
        for(int j = 0; j < 4; j++){
            if(dir == 0) r[j] = g[i][j];
            else if(dir == 1) r[j] = g[i][3-j];
            else if(dir == 2) r[j] = g[j][i];
            else             r[j] = g[3-j][i];
        }
        if(slide(r)) moved = 1;
        /* write results back with the same index mapping */
        for(int j = 0; j < 4; j++){
            if(dir == 0) g[i][j]   = r[j];
            else if(dir == 1) g[i][3-j] = r[j];
            else if(dir == 2) g[j][i]   = r[j];
            else             g[3-j][i]  = r[j];
        }
    }
    return moved;
}
  • Left (0): row i, columns left-to-right — identical to the natural layout.
  • Right (1): row i, columns reversed — slide pushes tiles to the high end.
  • Up (2): column i read top-to-bottom as a virtual row.
  • Down (3): column i read bottom-to-top as a virtual row.

The same index expression is used for the write-back, so the transposition is automatically undone.

6. The Rendering Pipeline

The renderer uses ANSI escape sequences to rewrite the screen in-place on every frame, avoiding flicker:

printf("\033[H 2048   score: %d\n\n", score);   /* move cursor to top-left */
for(int i = 0; i < 4; i++){
    for(int j = 0; j < 4; j++){
        if(g[i][j]) printf("%6d", g[i][j]);
        else        printf("     .");
    }
    printf("\n\n");
}

\033[2J (sent once at startup) clears the screen. Subsequent frames use \033[H to home the cursor and overwrite the same lines, so there is no scrolling or blinking. \033[?25l hides the cursor during play; \033[?25h (in raw_off) restores it on exit.

7. The Main Game Loop

The loop renders the board, then spins in an inner polling loop until a valid key arrives:

while(run){
    /* render ... */
    int did = 0; char c;
    while(!did){
        if(read(0, &c, 1) == 1){
            int d = -1;
            if(c=='a') d=0; else if(c=='d') d=1;
            else if(c=='w') d=2; else if(c=='s') d=3;
            else if(c=='q'){ run=0; break; }
            else if(c=='\033'){ /* arrow-key escape sequence */
                char a, b;
                if(read(0,&a,1)==1 && read(0,&b,1)==1){
                    if(b=='D') d=0; else if(b=='C') d=1;
                    else if(b=='A') d=2; else if(b=='B') d=3;
                } }
            if(d >= 0){ if(move(d)) spawn(); did=1; }
        } else usleep(10000);
    }
}

Arrow keys arrive as three-byte sequences \033 [ <letter>, where the letter encodes the direction (A=up, B=down, C=right, D=left). Because stdin is in non-blocking mode the two follow-up bytes are read immediately without risk of hanging.


Build

gcc tiles.c -o tiles

Run

./tiles

Controls

WASD or arrow keys to move, q to quit.

About

► 2048 sliding-tile game in the terminal, in C.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages