Task

Implement a 2D sliding block puzzle game where blocks with numbers are combined to add their values.



Rules of the game

   The rules are that on each turn the player must choose a direction   (up, down, left or right).
   All tiles move as far as possible in that direction, some move more than others. 
   Two adjacent tiles (in that direction only) with matching numbers combine into one bearing the sum of those numbers. 
   A move is valid when at least one tile can be moved,   if only by combination. 
   A new tile with the value of   2   is spawned at the end of each turn at a randomly chosen empty square   (if there is one). 
   Adding a new tile on a blank space.   Most of the time,   a new   2   is to be added,   and occasionally   (10% of the time),   a   4.
   To win,   the player must create a tile with the number   2048. 
   The player loses if no valid moves are possible.


The name comes from the popular open-source implementation of this game mechanic, 2048.



Requirements

   "Non-greedy" movement.   
  The tiles that were created by combining other tiles should not be combined again during the same turn (move).   
  That is to say,   that moving the tile row of:
               [2][2][2][2] 

 to the right should result in: 
               ......[4][4] 

 and not:
               .........[8] 

   "Move direction priority".   
  If more than one variant of combining is possible,   move direction shall indicate which combination will take effect. 
  For example, moving the tile row of:
               ...[2][2][2] 

 to the right should result in:
               ......[2][4] 

 and not:
               ......[4][2] 



   Check for valid moves.   The player shouldn't be able to skip their turn by trying a move that doesn't change the board.
   Check for a  win condition.
   Check for a lose condition.


 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
 
#define D_INVALID -1
#define D_UP       1
#define D_DOWN     2
#define D_RIGHT    3
#define D_LEFT     4
 
const long values[] = {
    0, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048
};
 
const char *colors[] = {
    "39", "31", "32", "33", "34", "35", "36", "37", "91", "92", "93", "94"
};
 
struct gamestate_struct__ {
    int grid[4][4];
    int have_moved;
    long total_score;
    long score_last_move;
    int blocks_in_play;
} game;
 
struct termios oldt, newt;
 
void do_draw(void)
{
    printf("\033[2J\033[HScore: %ld", game.total_score);
    if (game.score_last_move)
        printf(" (+%ld)", game.score_last_move);
    printf("\n");
 
    for (int i = 0; i < 25; ++i)
        printf("-");
    printf("\n");
 
    for (int y = 0; y < 4; ++y) {
        printf("|");
        for (int x = 0; x < 4; ++x) {
            if (game.grid[x][y])
                printf("\033[7m\033[%sm%*zd \033[0m|", colors[game.grid[x][y]],
                        4, values[game.grid[x][y]]);
            else
                printf("%*s |", 4, "");
        }
        printf("\n");
    }
 
    for (int i = 0; i < 25; ++i) {
        printf("-");
    }
    printf("\n");
}
 
void do_merge(int d)
{
/* These macros look pretty scary, but mainly demonstrate some space saving */
#define MERGE_DIRECTION(_v1, _v2, _xs, _xc, _xi, _ys, _yc, _yi, _x, _y)     \
    do {                                                                    \
        for (int _v1 = _xs; _v1 _xc; _v1 += _xi) {                          \
            for (int _v2 = _ys; _v2 _yc; _v2 += _yi) {                      \
                if (game.grid[x][y] && (game.grid[x][y] ==                  \
                                    game.grid[x + _x][y + _y])) {           \
                    game.grid[x][y] += (game.have_moved = 1);               \
                    game.grid[x + _x][y + _y] = (0 * game.blocks_in_play--);\
                    game.score_last_move += values[game.grid[x][y]];        \
                    game.total_score += values[game.grid[x][y]];            \
                }                                                           \
            }                                                               \
        }                                                                   \
    } while (0)
 
    game.score_last_move = 0;
 
    switch (d) {
        case D_LEFT:
            MERGE_DIRECTION(x, y, 0, < 3, 1, 0, < 4, 1, 1, 0);
            break;
        case D_RIGHT:
            MERGE_DIRECTION(x, y, 3, > 0, -1, 0, < 4, 1, -1, 0);
            break;
        case D_DOWN:
            MERGE_DIRECTION(y, x, 3, > 0, -1, 0, < 4, 1, 0, -1);
            break;
        case D_UP:
            MERGE_DIRECTION(y, x, 0, < 3, 1, 0, < 4, 1, 0, 1);
            break;
    }
 
#undef MERGE_DIRECTION
}
 
void do_gravity(int d)
{
#define GRAVITATE_DIRECTION(_v1, _v2, _xs, _xc, _xi, _ys, _yc, _yi, _x, _y) \
    do {                                                                    \
        int break_cond = 0;                                                 \
        while (!break_cond) {                                               \
            break_cond = 1;                                                 \
            for (int _v1 = _xs; _v1 _xc; _v1 += _xi) {                      \
                for (int _v2 = _ys; _v2 _yc; _v2 += _yi) {                  \
                    if (!game.grid[x][y] && game.grid[x + _x][y + _y]) {    \
                        game.grid[x][y] = game.grid[x + _x][y + _y];        \
                        game.grid[x + _x][y + _y] = break_cond = 0;         \
                        game.have_moved = 1;                                \
                    }                                                       \
                }                                                           \
            }                                                               \
            do_draw(); usleep(40000);                                       \
        }                                                                   \
    } while (0)
 
    switch (d) {
        case D_LEFT:
            GRAVITATE_DIRECTION(x, y, 0, < 3, 1, 0, < 4, 1, 1, 0);
            break;
        case D_RIGHT:
            GRAVITATE_DIRECTION(x, y, 3, > 0, -1, 0, < 4, 1, -1, 0);
            break;
        case D_DOWN:
            GRAVITATE_DIRECTION(y, x, 3, > 0, -1, 0, < 4, 1, 0, -1);
            break;
        case D_UP:
            GRAVITATE_DIRECTION(y, x, 0, < 3, 1, 0, < 4, 1, 0, 1);
            break;
    }
 
#undef GRAVITATE_DIRECTION
}
 
int do_check_end_condition(void)
{
    int ret = -1;
    for (int x = 0; x < 4; ++x) {
        for (int y = 0; y < 4; ++y) {
            if (values[game.grid[x][y]] == 2048)
                return 1;
            if (!game.grid[x][y] ||
                  ((x + 1 < 4) && (game.grid[x][y] == game.grid[x + 1][y])) ||
                  ((y + 1 < 4) && (game.grid[x][y] == game.grid[x][y + 1])))
                ret = 0;
        }
    }
    return ret;
}
 
int do_tick(int d)
{
    game.have_moved = 0;
    do_gravity(d);
    do_merge(d);
    do_gravity(d);
    return game.have_moved;
}
 
void do_newblock(void) {
    if (game.blocks_in_play >= 16) return;
 
    int bn = rand() % (16 - game.blocks_in_play);
    int pn = 0;
 
    for (int x = 0; x < 4; ++x) {
        for (int y = 0; y < 4; ++y) {
            if (game.grid[x][y])
                continue;
 
            if (pn == bn){
                game.grid[x][y] = rand() % 10 ? 1 : 2;
                game.blocks_in_play += 1;
                return;
            }
            else {
                ++pn;
            }
        }
    }
}
 
int main(void)
{
    /* Initialize terminal settings */
    tcgetattr(STDIN_FILENO, &oldt);
    newt = oldt;
    newt.c_lflag &= ~(ICANON | ECHO);
    tcsetattr(STDIN_FILENO, TCSANOW, &newt);
 
    srand(time(NULL));
    memset(&game, 0, sizeof(game));
    do_newblock();
    do_newblock();
    do_draw();
 
    while (1) {
        int found_valid_key, direction, value;
        do {
            found_valid_key = 1;
            direction       = D_INVALID;
            value           = getchar();
            switch (value) {
                case 'h': case 'a':
                    direction = D_LEFT;
                    break;
                case 'l': case 'd':
                    direction = D_RIGHT;
                    break;
                case 'j': case 's':
                    direction = D_DOWN;
                    break;
                case 'k': case 'w':
                    direction = D_UP;
                    break;
                case 'q':
                    goto game_quit;
                    break;
                case 27:
                    if (getchar() == 91) {
                        value = getchar();
                        switch (value) {
                            case 65:
                                direction = D_UP;
                                break;
                            case 66:
                                direction = D_DOWN;
                                break;
                            case 67:
                                direction = D_RIGHT;
                                break;
                            case 68:
                                direction = D_LEFT;
                                break;
                            default:
                                found_valid_key = 0;
                                break;
                        }
                    }
                    break;
                default:
                    found_valid_key = 0;
                    break;
            }
        }  while (!found_valid_key);
 
        do_tick(direction);
        if (game.have_moved != 0){
                do_newblock();
        }
        do_draw();
 
        switch (do_check_end_condition()) {
            case -1:
                goto game_lose;
            case 1:
                goto game_win;
            case 0:
                break;
        }
    }
 
    if (0)
game_lose:
    printf("You lose!\n");
    goto game_quit;
    if (0)
game_win:
    printf("You win!\n");
    goto game_quit;
    if (0)
game_quit:
 
    /* Restore terminal settings */
    tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
    return 0;
}
 