diff --git a/src/2048_engine.c b/src/2048_engine.c new file mode 100644 index 0000000..a183c70 --- /dev/null +++ b/src/2048_engine.c @@ -0,0 +1,246 @@ +#include +#include +#include "2048_engine.h" + +void gravitate(struct gamestate *g, direction d, void (*callback)(struct gamestate *g)) +{ + +#define swap_if_space(xoff, yoff)\ + do {\ + if (g->grid[x][y] == 0 && g->grid[x+xoff][y+yoff] != 0) {\ + g->grid[x][y] = g->grid[x+xoff][y+yoff];\ + g->grid[x+xoff][y+yoff] = 0;\ + done = 0;\ + g->moved = 1;\ + }\ + } while (0) + + int x, y; + int done = 0; + + if (d == dir_left) { + while (!done) { + done = 1; + for (x = 0; x < g->opts->grid_width - 1; ++x) { + for (y = 0; y < g->opts->grid_height; ++y) { + swap_if_space(1, 0); + } + } + if (callback) + callback(g); + } + } + else if (d == dir_right) { + while (!done) { + done = 1; + for (x = g->opts->grid_width - 1; x > 0; --x) { + for (y = 0; y < g->opts->grid_height; ++y) { + swap_if_space(-1, 0); + } + } + if (callback) + callback(g); + } + } + else if (d == dir_down) { + while (!done) { + done = 1; + for (y = g->opts->grid_height - 1; y > 0; --y) { + for (x = 0; x < g->opts->grid_width; ++x) { + swap_if_space(0, -1); + } + } + if (callback) + callback(g); + } + } + else if (d == dir_up) { + while (!done) { + done = 1; + for (y = 0; y < g->opts->grid_height - 1; ++y) { + for (x = 0; x < g->opts->grid_width; ++x) { + swap_if_space(0, 1); + } + } + if (callback) + callback(g); + } + } + else { + fatal("Invalid direction passed to gravitate()"); + /* Not reached */ + } + +#undef swap_if_space + +} + +void merge(struct gamestate *g, direction d, void (*callback)(struct gamestate *g)) +{ + +#define merge_if_equal(xoff, yoff)\ + do {\ + if (g->grid[x][y] && (g->grid[x][y] == g->grid[x+xoff][y+yoff])) {\ + g->grid[x][y] += g->grid[x+xoff][y+yoff];\ + g->grid[x+xoff][y+yoff] = 0;\ + g->score_last = g->grid[x][y];\ + g->score += g->grid[x][y];\ + g->moved = 1;\ + }\ + } while (0) + + int x, y; + + if (d == dir_left) { + for (x = 0; x < g->opts->grid_width - 1; ++x) { + for (y = 0; y < g->opts->grid_height; ++y) { + merge_if_equal(1, 0); + } + } + } + else if (d == dir_right) { + for (x = g->opts->grid_width - 1; x > 0; --x) { + for (y = 0; y < g->opts->grid_height; ++y) { + merge_if_equal(-1, 0); + } + } + } + else if (d == dir_down) { + for (y = g->opts->grid_height - 1; y > 0; --y) { + for (x = 0; x < g->opts->grid_width; ++x) { + merge_if_equal(0, -1); + } + } + } + else if (d == dir_up) { + for (y = 0; y < g->opts->grid_height - 1; ++y) { + for (x = 0; x < g->opts->grid_width; ++x) { + merge_if_equal(0, 1); + } + } + } + else { + fatal("Invalid direction passed to merge()"); + /* Not reached */ + } + + if (callback) + callback(g); + +#undef merge_if_equal +} + +int moves_available(struct gamestate *g) +{ + int x, y; + for (x = 0; x < g->opts->grid_width; ++x) { + for (y = 0; y < g->opts->grid_height; ++y) { + if (!g->grid[x][y] || ((x + 1 < g->opts->grid_width) && + (g->grid[x][y] == g->grid[x+1][y])) + || ((y + 1 < g->opts->grid_height) && + (g->grid[x][y] == g->grid[x][y+1]))) + return 1; + } + } + + return 0; +} + +void random_block(struct gamestate *g) +{ + /* pick random square, if it is full, then move forward until we find + * an empty square. This is biased */ + + static int seeded = 0; + if (!seeded) { + seeded = 1; + srand(time(NULL)); + } + + int x = rand() % g->opts->grid_width; + int y = rand() % g->opts->grid_height; + + while (g->grid[x][y]) { + x++; + if (x == g->opts->grid_width) { + x = 0; y++; + if (y == g->opts->grid_height) + y = 0; + } + } + + g->grid[x][y] = (rand() & 3) ? g->opts->spawn_value : g->opts->spawn_value * 2; +} + +static int flog10(unsigned int n) +{ + int l = 0; + while (n) n /= 10, ++l; + return l; +} + +struct gamestate* gamestate_init(struct gameoptions *opt) +{ + if (!opt) return NULL; + + struct gamestate *g = malloc(sizeof(struct gamestate)); + if (!g) goto gamestate_alloc_fail; + + //long *grid_back = malloc(opt->grid_width * opt->grid_height * sizeof(long)); + //if (!grid_back) goto grid_back_alloc_fail; + + g->grid = malloc(opt->grid_width * sizeof(long*)); + if (!g->grid) goto grid_alloc_fail; + + int i; + for (i = 0; i < opt->grid_height; ++i) + g->grid[i] = malloc(opt->grid_height * sizeof(long)); + + g->moved = 0; + g->score = 0; + g->score_high = 0; + g->score_last = 0; + g->print_width = flog10(opt->goal); + g->opts = opt; + + random_block(g); + return g; + +grid_alloc_fail: +grid_back_alloc_fail: + free(g); +gamestate_alloc_fail: + return NULL; +} + +struct gameoptions* gameoptions_default(void) +{ + struct gameoptions *opt = malloc(sizeof(struct gameoptions)); + if (!opt) return NULL; + + opt->grid_height = 4; + opt->grid_width = 4; + opt->goal = 2048; + opt->spawn_value = 2; + opt->spawn_rate = 1; + + return opt; +} + +int gamestate_tick(struct gamestate *g, direction d, void (*callback)(struct gamestate*)) +{ + g->moved = 0; + gravitate(g, d, callback); + merge(g, d, callback); + gravitate(g, d, callback); + random_block(g); + return g->moved; +} + +void gamestate_clear(struct gamestate *g) +{ + free(g->opts); + free(g->grid[0]); + free(g->grid); + free(g); +} diff --git a/src/2048_engine.h b/src/2048_engine.h new file mode 100644 index 0000000..015c34e --- /dev/null +++ b/src/2048_engine.h @@ -0,0 +1,46 @@ +#ifndef _2048_ENGINE +#define _2048_ENGINE + +#include + +#define fatal(msg) do { fprintf(stderr, "line %d: %s\n", __LINE__, msg); abort(); } while (0) + +typedef enum { + dir_left = 'h', + dir_right = 'l', + dir_up = 'k', + dir_down = 'j' +} direction; + +struct gameoptions { + size_t grid_height; + size_t grid_width; + long goal; + long spawn_value; + int spawn_rate; +}; + +struct gamestate { + /* Game state */ + long **grid; + int moved; + long score; + long score_high; + long score_last; + int print_width; + /* Options */ + struct gameoptions *opts; + /* Draw functions */ + void (*ds_draw)(struct gamestate*); + void (*ds_clear)(void); +}; + +void gravitate(struct gamestate*, direction, void (*callback)(struct gamestate*)); +void merge(struct gamestate*, direction, void (*callback)(struct gamestate*)); +int moves_available(struct gamestate *); +void random_block(struct gamestate *); +int gamestate_tick(struct gamestate*, direction, void (*callback)(struct gamestate*)); +void gamestate_clear(struct gamestate*);; +struct gamestate* gamestate_init(struct gameoptions *); +struct gameoptions* gameoptions_default(void); +#endif diff --git a/src/2048_rewrite.c b/src/2048_rewrite.c new file mode 100644 index 0000000..281e5f7 --- /dev/null +++ b/src/2048_rewrite.c @@ -0,0 +1,152 @@ +#include +#include +#include +#include +#include +#include +#include "2048_engine.h" + +#define ITER(x, expr)\ + do {\ + int i;\ + for (i = 0; i < x; ++i) { expr; }\ + } while (0) + +#ifdef HAVE_CURSES +void drawstate_init(void) +{ + initscr(); + cbreak(); + noecho(); + curs_set(FALSE); +} + +void drawstate_clear(void) +{ + endwin(); +} + +void draw_screen(struct gamestate *g) +{ + static WINDOW *gamewin; + static size_t wh; + static size_t ww; + + if (!gamewin) { + wh = g->opts->grid_height * (g->print_width + 2) + 3; + ww = g->opts->grid_width * (g->print_width + 2) + 1; + gamewin = newwin(wh, ww, 1, 1); + keypad(gamewin, TRUE); + } + + // mvwprintw will sometimes have a useless arg, this is warned, but doesn't affect the program + char *scr = g->score_last ? "SCORE: %d (+%d)\n" : "SCORE: %d\n"; + mvwprintw(gamewin, 0, 0, scr, g->score, g->score_last); + mvwprintw(gamewin, 1, 0, "HISCR: %d\n", g->score_high); + + ITER(g->opts->grid_width*(g->print_width + 2) + 1, waddch(gamewin, '-')); + int i, j, xps = 0, yps = 3; + for (i = 0; i < g->opts->grid_width; i++, xps = 0, yps++) { + mvwprintw(gamewin, yps, xps++, "|"); + for (j = 0; j < g->opts->grid_height; j++) { + if (g->grid[j][i]) { + mvwprintw(gamewin, yps, xps, "%*d", g->print_width, g->grid[j][i]); + mvwprintw(gamewin, yps, xps + g->print_width, " |"); + } + else { + ITER(g->print_width + 1, waddch(gamewin, ' ')); + waddch(gamewin, '|'); + } + xps += (g->print_width + 2); + } + } + ITER(g->opts->grid_height*(g->print_width + 2) + 1, waddch(gamewin, '-')); + wrefresh(gamewin); +} + +int get_keypress(void) +{ + return getch(); +} + +#elif VT100_COMPATIBLE + +#else +struct termios sattr; +void drawstate_clear() +{ + tcsetattr(STDIN_FILENO, TCSANOW, &sattr); +} + +void drawstate_init(void) +{ + tcgetattr(STDIN_FILENO, &sattr); + + /* alters terminal stdin to not echo and doesn't need \n before reading getchar */ + struct termios tattr; + tcgetattr(STDIN_FILENO, &tattr); + tattr.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDOUT_FILENO, TCSANOW, &tattr); +} + +void draw_screen(struct gamestate *g) +{ + printf("HISCORE: %ld |", g->score_high); + printf("| SCORE: %ld ", g->score); + if (g->score_last) printf("(+%ld)", g->score_last); + printf("\n"); + + // alter this grid_size + 1 to match abitrary grid size + ITER(g->opts->grid_width, printf("------")); + printf("-\n"); + int i, j; + for (i = 0; i < g->opts->grid_height; i++) { + printf("|"); + for (j = 0; j < g->opts->grid_width; j++) { + if (g->grid[j][i]) + printf("%*ld |", 4, g->grid[j][i]); + else + printf(" |"); + } + printf("\n"); + } + ITER(g->opts->grid_width, printf("------")); + printf("-\n\n"); +} + +int get_keypress(void) +{ + return fgetc(stdin); +} + +#endif + +int main(int argc, char **argv) +{ + struct gamestate *g = gamestate_init(gameoptions_default()); + + drawstate_init(); + + while (1) { + draw_screen(g); + + /* abstract getting keypress */ + int ch; + do { + ch = get_keypress(); + if (ch == 'q') { goto endloop; } + } while (strchr("hjkl", ch) == NULL); + + gamestate_tick(g, ch, NULL); + + if (!moves_available(g)) { + printf("You lose\n"); + break; + } + } +endloop: + + drawstate_clear(); + gamestate_clear(g); + return 0; +}