From 387f37a64af9d1ef922652d6a31a015efcb6f457 Mon Sep 17 00:00:00 2001 From: Marc Date: Fri, 5 Dec 2014 10:27:25 +1300 Subject: [PATCH 01/19] Abstract engine and drawing logic apart from one another. Keep gamestate in a struct --- src/2048_engine.c | 246 +++++++++++++++++++++++++++++++++++++++++++++ src/2048_engine.h | 46 +++++++++ src/2048_rewrite.c | 152 ++++++++++++++++++++++++++++ 3 files changed, 444 insertions(+) create mode 100644 src/2048_engine.c create mode 100644 src/2048_engine.h create mode 100644 src/2048_rewrite.c 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; +} From 2b1d2303307e40914c0e411552a979f17c6a82e8 Mon Sep 17 00:00:00 2001 From: Tiehuis Date: Sat, 6 Dec 2014 11:20:13 +1300 Subject: [PATCH 02/19] Alter looping variables to better describe intent --- src/2048_rewrite.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/2048_rewrite.c b/src/2048_rewrite.c index 281e5f7..a3e8265 100644 --- a/src/2048_rewrite.c +++ b/src/2048_rewrite.c @@ -45,12 +45,12 @@ void draw_screen(struct gamestate *g) 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++) { + int x, y, xps = 0, yps = 3; + for (y = 0; y < g->opts->grid_height; y++, 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]); + for (x = 0; x < g->opts->grid_width; x++) { + if (g->grid[x][y]) { + mvwprintw(gamewin, yps, xps, "%*d", g->print_width, g->grid[x][y]); mvwprintw(gamewin, yps, xps + g->print_width, " |"); } else { @@ -99,12 +99,12 @@ void draw_screen(struct gamestate *g) // 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++) { + int x, y; + for (y = 0; y < g->opts->grid_height; y++) { printf("|"); - for (j = 0; j < g->opts->grid_width; j++) { - if (g->grid[j][i]) - printf("%*ld |", 4, g->grid[j][i]); + for (x = 0; x < g->opts->grid_width; x++) { + if (g->grid[x][y]) + printf("%*ld |", 4, g->grid[x][y]); else printf(" |"); } From 01af91121b80d82f2c9b6ec11ef8732f31d65f5b Mon Sep 17 00:00:00 2001 From: Tiehuis Date: Sat, 6 Dec 2014 11:49:16 +1300 Subject: [PATCH 03/19] Fix curses not displaying until keypress. Callback example demonstrating some pseudo-animation --- src/2048_rewrite.c | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/src/2048_rewrite.c b/src/2048_rewrite.c index a3e8265..bc7c6b5 100644 --- a/src/2048_rewrite.c +++ b/src/2048_rewrite.c @@ -13,19 +13,6 @@ } 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; @@ -64,6 +51,20 @@ void draw_screen(struct gamestate *g) wrefresh(gamewin); } +void drawstate_init(void) +{ + initscr(); + cbreak(); + noecho(); + curs_set(FALSE); + refresh(); +} + +void drawstate_clear(void) +{ + endwin(); +} + int get_keypress(void) { return getch(); @@ -104,7 +105,7 @@ void draw_screen(struct gamestate *g) printf("|"); for (x = 0; x < g->opts->grid_width; x++) { if (g->grid[x][y]) - printf("%*ld |", 4, g->grid[x][y]); + printf("%*ld |", g->print_width, g->grid[x][y]); else printf(" |"); } @@ -121,6 +122,12 @@ int get_keypress(void) #endif +void ddraw(struct gamestate *g) +{ + draw_screen(g); + usleep(30000); +} + int main(int argc, char **argv) { struct gamestate *g = gamestate_init(gameoptions_default()); @@ -137,12 +144,12 @@ int main(int argc, char **argv) if (ch == 'q') { goto endloop; } } while (strchr("hjkl", ch) == NULL); - gamestate_tick(g, ch, NULL); - if (!moves_available(g)) { printf("You lose\n"); break; } + + gamestate_tick(g, ch, ddraw); } endloop: From 569e11710f5b8f7525f8cf5243500a87b72b1d08 Mon Sep 17 00:00:00 2001 From: Tiehuis Date: Sat, 6 Dec 2014 12:02:12 +1300 Subject: [PATCH 04/19] vt100 simple mode added --- src/2048_engine.c | 3 ++- src/2048_rewrite.c | 23 +++++++++++++---------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/2048_engine.c b/src/2048_engine.c index a183c70..5001f1b 100644 --- a/src/2048_engine.c +++ b/src/2048_engine.c @@ -83,13 +83,14 @@ void merge(struct gamestate *g, direction d, void (*callback)(struct gamestate * 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_last += g->grid[x][y];\ g->score += g->grid[x][y];\ g->moved = 1;\ }\ } while (0) int x, y; + g->score_last = 0; if (d == dir_left) { for (x = 0; x < g->opts->grid_width - 1; ++x) { diff --git a/src/2048_rewrite.c b/src/2048_rewrite.c index bc7c6b5..98ccc52 100644 --- a/src/2048_rewrite.c +++ b/src/2048_rewrite.c @@ -70,9 +70,7 @@ int get_keypress(void) return getch(); } -#elif VT100_COMPATIBLE - -#else +#else /* vt100 and standard shared functions */ struct termios sattr; void drawstate_clear() { @@ -90,8 +88,19 @@ void drawstate_init(void) tcsetattr(STDOUT_FILENO, TCSANOW, &tattr); } +int get_keypress(void) +{ + return fgetc(stdin); +} + + void draw_screen(struct gamestate *g) { + /* Clear the screen each draw if we are able to */ +#ifdef VT100_COMPATIBLE + printf("\033[2J\033[H"); +#endif + printf("HISCORE: %ld |", g->score_high); printf("| SCORE: %ld ", g->score); if (g->score_last) printf("(+%ld)", g->score_last); @@ -114,13 +123,7 @@ void draw_screen(struct gamestate *g) ITER(g->opts->grid_width, printf("------")); printf("-\n\n"); } - -int get_keypress(void) -{ - return fgetc(stdin); -} - -#endif +#endif /* CURSES */ void ddraw(struct gamestate *g) { From b7c45f7f87575af6154724f4899e472c0cd309b7 Mon Sep 17 00:00:00 2001 From: Tiehuis Date: Sat, 6 Dec 2014 12:57:07 +1300 Subject: [PATCH 05/19] Add some option mechanics --- src/2048_engine.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++ src/2048_engine.h | 6 ++-- src/2048_rewrite.c | 23 ++++++++------- 3 files changed, 87 insertions(+), 14 deletions(-) diff --git a/src/2048_engine.c b/src/2048_engine.c index 5001f1b..118b4fe 100644 --- a/src/2048_engine.c +++ b/src/2048_engine.c @@ -224,6 +224,8 @@ struct gameoptions* gameoptions_default(void) opt->goal = 2048; opt->spawn_value = 2; opt->spawn_rate = 1; + opt->enable_color = 0; + opt->animate = 1; return opt; } @@ -245,3 +247,73 @@ void gamestate_clear(struct gamestate *g) free(g->grid); free(g); } + +/* The following may be moved into own file */ +void reset_highscore(void) +{ + printf("Are you sure you want to reset your highscores? (Y)es/(N)o: "); + + int response; + if ((response = getchar()) == 'y' || response == 'Y') { + printf("Resetting highscore...\n"); + } +} + +void print_usage(void) +{ + printf( + "usage: 2048 [-cCrh] [-b ] [-s ]\n" + "\n" + "controls\n" + " hjkl movement keys\n" + " q quit current game\n" + "\n" + "options\n" + " -s set the grid side lengths\n" + " -b set the block spawn rate\n" + " -a enable animations (default)\n" + " -A disable animations\n" + " -c enable color support\n" + " -C disable color support (default)\n" + ); +} + +#include + +struct gameoptions* parse_options(struct gameoptions *opt, int argc, char **argv) +{ + int c; + while ((c = getopt(argc, argv, "aArcChs:b:")) != -1) { + switch (c) { + case 'a': + opt->animate = 1; + break; + case 'A': + opt->animate = 0; + break; + case 'c': + opt->enable_color = 1; + break; + case 'C': + opt->enable_color = 0; + break; + case 's':; + /* Stick with square for now */ + int optint = strtol(optarg, NULL, 10); + opt->grid_height = optint > 4 ? optint : 4; + opt->grid_width = optint > 4 ? optint : 4; + break; + case 'b': + opt->spawn_rate = strtol(optarg, NULL, 10); + break; + case 'r': + reset_highscore(); + exit(0); + case 'h': + print_usage(); + exit(0); + } + } + + return opt; +} diff --git a/src/2048_engine.h b/src/2048_engine.h index 015c34e..cb9b277 100644 --- a/src/2048_engine.h +++ b/src/2048_engine.h @@ -18,6 +18,8 @@ struct gameoptions { long goal; long spawn_value; int spawn_rate; + int enable_color; + int animate; }; struct gamestate { @@ -30,11 +32,9 @@ struct gamestate { int print_width; /* Options */ struct gameoptions *opts; - /* Draw functions */ - void (*ds_draw)(struct gamestate*); - void (*ds_clear)(void); }; +struct gameoptions* parse_options(struct gameoptions*, int, char**); void gravitate(struct gamestate*, direction, void (*callback)(struct gamestate*)); void merge(struct gamestate*, direction, void (*callback)(struct gamestate*)); int moves_available(struct gamestate *); diff --git a/src/2048_rewrite.c b/src/2048_rewrite.c index 98ccc52..fc4eee2 100644 --- a/src/2048_rewrite.c +++ b/src/2048_rewrite.c @@ -6,7 +6,7 @@ #include #include "2048_engine.h" -#define ITER(x, expr)\ +#define iterate(x, expr)\ do {\ int i;\ for (i = 0; i < x; ++i) { expr; }\ @@ -31,7 +31,7 @@ void draw_screen(struct gamestate *g) 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, '-')); + iterate(g->opts->grid_width*(g->print_width + 2) + 1, waddch(gamewin, '-')); int x, y, xps = 0, yps = 3; for (y = 0; y < g->opts->grid_height; y++, xps = 0, yps++) { mvwprintw(gamewin, yps, xps++, "|"); @@ -41,13 +41,13 @@ void draw_screen(struct gamestate *g) mvwprintw(gamewin, yps, xps + g->print_width, " |"); } else { - ITER(g->print_width + 1, waddch(gamewin, ' ')); + iterate(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, '-')); + iterate(g->opts->grid_height*(g->print_width + 2) + 1, waddch(gamewin, '-')); wrefresh(gamewin); } @@ -93,7 +93,6 @@ int get_keypress(void) return fgetc(stdin); } - void draw_screen(struct gamestate *g) { /* Clear the screen each draw if we are able to */ @@ -107,7 +106,7 @@ void draw_screen(struct gamestate *g) printf("\n"); // alter this grid_size + 1 to match abitrary grid size - ITER(g->opts->grid_width, printf("------")); + iterate(g->opts->grid_width, printf("------")); printf("-\n"); int x, y; for (y = 0; y < g->opts->grid_height; y++) { @@ -120,7 +119,7 @@ void draw_screen(struct gamestate *g) } printf("\n"); } - ITER(g->opts->grid_width, printf("------")); + iterate(g->opts->grid_width, printf("------")); printf("-\n\n"); } #endif /* CURSES */ @@ -128,15 +127,17 @@ void draw_screen(struct gamestate *g) void ddraw(struct gamestate *g) { draw_screen(g); - usleep(30000); + usleep(40000); } int main(int argc, char **argv) { - struct gamestate *g = gamestate_init(gameoptions_default()); + struct gamestate *g = gamestate_init( + parse_options( + gameoptions_default(), argc, argv)); drawstate_init(); - + while (1) { draw_screen(g); @@ -152,7 +153,7 @@ int main(int argc, char **argv) break; } - gamestate_tick(g, ch, ddraw); + gamestate_tick(g, ch, g->opts->animate ? ddraw : NULL); } endloop: From 44bc1cbe87bf839afd8488eb2b0c7071d82e339b Mon Sep 17 00:00:00 2001 From: Tiehuis Date: Sat, 6 Dec 2014 12:58:29 +1300 Subject: [PATCH 06/19] correct grid malloc to calloc --- src/2048_engine.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/2048_engine.c b/src/2048_engine.c index 118b4fe..cb0306e 100644 --- a/src/2048_engine.c +++ b/src/2048_engine.c @@ -190,12 +190,12 @@ struct gamestate* gamestate_init(struct gameoptions *opt) //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*)); + 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->grid[i] = calloc(opt->grid_height, sizeof(long)); g->moved = 0; g->score = 0; From 3ca10eb8d5492d451650c45a48040331d6a11433 Mon Sep 17 00:00:00 2001 From: Tiehuis Date: Sat, 6 Dec 2014 13:00:47 +1300 Subject: [PATCH 07/19] Correct last commit --- src/2048_engine.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/2048_engine.c b/src/2048_engine.c index cb0306e..470db61 100644 --- a/src/2048_engine.c +++ b/src/2048_engine.c @@ -190,7 +190,7 @@ struct gamestate* gamestate_init(struct gameoptions *opt) //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*)); + g->grid = malloc(opt->grid_width * sizeof(long*)); if (!g->grid) goto grid_alloc_fail; int i; From 32a4b483ee22a717745a1fc45bcab11a3ba1129d Mon Sep 17 00:00:00 2001 From: Tiehuis Date: Sat, 6 Dec 2014 13:43:00 +1300 Subject: [PATCH 08/19] Consolidate printing styles, make more generic. Add goal option to create a custom target --- src/2048_engine.c | 20 +++++++++++++++----- src/2048_engine.h | 2 +- src/2048_rewrite.c | 31 ++++++++++++++++--------------- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/2048_engine.c b/src/2048_engine.c index 470db61..2a93fe8 100644 --- a/src/2048_engine.c +++ b/src/2048_engine.c @@ -131,20 +131,26 @@ void merge(struct gamestate *g, direction d, void (*callback)(struct gamestate * #undef merge_if_equal } -int moves_available(struct gamestate *g) +/* Return -1 on lose condition, 1 on win condition, 0 on + * haven't ended */ +int end_condition(struct gamestate *g) { + int ret = -1; + 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] >= g->opts->goal) + return 1; 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; + ret = 0; } } - return 0; + return ret; } void random_block(struct gamestate *g) @@ -262,7 +268,7 @@ void reset_highscore(void) void print_usage(void) { printf( - "usage: 2048 [-cCrh] [-b ] [-s ]\n" + "usage: 2048 [-cCaArh] [-g ] [-b ] [-s ]\n" "\n" "controls\n" " hjkl movement keys\n" @@ -271,6 +277,7 @@ void print_usage(void) "options\n" " -s set the grid side lengths\n" " -b set the block spawn rate\n" + " -g set a new goal (default 2048)\n" " -a enable animations (default)\n" " -A disable animations\n" " -c enable color support\n" @@ -283,7 +290,7 @@ void print_usage(void) struct gameoptions* parse_options(struct gameoptions *opt, int argc, char **argv) { int c; - while ((c = getopt(argc, argv, "aArcChs:b:")) != -1) { + while ((c = getopt(argc, argv, "aArcChg:s:b:")) != -1) { switch (c) { case 'a': opt->animate = 1; @@ -297,6 +304,9 @@ struct gameoptions* parse_options(struct gameoptions *opt, int argc, char **argv case 'C': opt->enable_color = 0; break; + case 'g': + opt->goal = strtol(optarg, NULL, 10); + break; case 's':; /* Stick with square for now */ int optint = strtol(optarg, NULL, 10); diff --git a/src/2048_engine.h b/src/2048_engine.h index cb9b277..55ba72b 100644 --- a/src/2048_engine.h +++ b/src/2048_engine.h @@ -37,7 +37,7 @@ struct gamestate { struct gameoptions* parse_options(struct gameoptions*, int, char**); void gravitate(struct gamestate*, direction, void (*callback)(struct gamestate*)); void merge(struct gamestate*, direction, void (*callback)(struct gamestate*)); -int moves_available(struct gamestate *); +int end_condition(struct gamestate *); void random_block(struct gamestate *); int gamestate_tick(struct gamestate*, direction, void (*callback)(struct gamestate*)); void gamestate_clear(struct gamestate*);; diff --git a/src/2048_rewrite.c b/src/2048_rewrite.c index fc4eee2..80f3f60 100644 --- a/src/2048_rewrite.c +++ b/src/2048_rewrite.c @@ -99,15 +99,13 @@ void draw_screen(struct gamestate *g) #ifdef VT100_COMPATIBLE printf("\033[2J\033[H"); #endif - - printf("HISCORE: %ld |", g->score_high); - printf("| SCORE: %ld ", g->score); - if (g->score_last) printf("(+%ld)", g->score_last); - printf("\n"); + char *scr = g->score_last ? "SCORE: %d (+%d)\n" : "SCORE: %d\n"; + printf(scr, g->score, g->score_last); + printf("HISCR: %ld\n", g->score_high); // alter this grid_size + 1 to match abitrary grid size - iterate(g->opts->grid_width, printf("------")); - printf("-\n"); + iterate((g->print_width + 2) * g->opts->grid_width + 1, printf("-")); + printf("\n"); int x, y; for (y = 0; y < g->opts->grid_height; y++) { printf("|"); @@ -115,12 +113,13 @@ void draw_screen(struct gamestate *g) if (g->grid[x][y]) printf("%*ld |", g->print_width, g->grid[x][y]); else - printf(" |"); + printf("%*s |", g->print_width, ""); } printf("\n"); } - iterate(g->opts->grid_width, printf("------")); - printf("-\n\n"); + + iterate((g->print_width + 2) * g->opts->grid_width + 1, printf("-")); + printf("\n\n"); } #endif /* CURSES */ @@ -141,6 +140,13 @@ int main(int argc, char **argv) while (1) { draw_screen(g); + int e; + if ((e = end_condition(g))) { + drawstate_clear(); + printf(e > 0 ? "You win\n" : "You lose\n"); + goto endloop; + } + /* abstract getting keypress */ int ch; do { @@ -148,11 +154,6 @@ int main(int argc, char **argv) if (ch == 'q') { goto endloop; } } while (strchr("hjkl", ch) == NULL); - if (!moves_available(g)) { - printf("You lose\n"); - break; - } - gamestate_tick(g, ch, g->opts->animate ? ddraw : NULL); } endloop: From a116923c69e02ae34d8307908d4b21763d3118f5 Mon Sep 17 00:00:00 2001 From: Tiehuis Date: Sat, 6 Dec 2014 13:49:54 +1300 Subject: [PATCH 09/19] Clean devel src dir --- src/2048.c | 387 ------------------------------------------- src/2048.h | 65 -------- src/2048_no_curses.c | 326 ------------------------------------ src/highscore_file.c | 29 ---- 4 files changed, 807 deletions(-) delete mode 100644 src/2048.c delete mode 100644 src/2048.h delete mode 100644 src/2048_no_curses.c delete mode 100644 src/highscore_file.c diff --git a/src/2048.c b/src/2048.c deleted file mode 100644 index 558b432..0000000 --- a/src/2048.c +++ /dev/null @@ -1,387 +0,0 @@ -/* - * 2048.c - Main version that should be worked on. - * - * Things to do: - * - Finish cleaning up code. - * - Ask user if they would like to play again instead of exiting immediately. - * - Add an actual goal (what happens when we reach 2048?). - * - Keep track list of past scores, and store in a better fashion independent - * of cwd. Also, store in not in plaintext. - * */ - -#include /* for malloc */ -#include /* for time */ -#include /* for getopts */ -#include -#include "2048.h" - -int **grid; /* grid pointer */ -int grid_size; /* grid size */ -int score; /* score */ -int score_last; /* Score for last move */ -int score_high; /* Hiscore */ -int printwidth; /* maximum length of any value in grid, for printing */ - -/* Merges adjacent squares of the same value together in a certain direction */ -int merge(dir_t d) -{ - int moved = 0; - int i, j; - - if (d == DIR_LEFT) { - /* Move from upper left, across rows until second to second-last elem each row */ - for (i = 0; i < grid_size; i++) { - for (j = 0; j < grid_size - 1; j++) { - if (grid[i][j] == grid[i][j + 1]) { - grid[i][j] <<= 1; - grid[i][j + 1] = 0; - score_last += grid[i][j]; - score += grid[i][j]; - moved = 1; - } - } - } - } - else if (d == DIR_UP) { - /* Move from upper left, across rows until final row which is skipped */ - for (i = 0; i < grid_size - 1; i++) { - for (j = 0; j < grid_size; j++) { - if (grid[i][j] == grid[i + 1][j]) { - grid[i][j] <<= 1; - grid[i + 1][j] = 0; - score_last += grid[i][j]; - score += grid[i][j]; - moved = 1; - } - } - } - } - else if (d == DIR_RIGHT) { - /* Move from lower right, backwards across rows until first elem each row */ - for (i = grid_size - 1; i >= 0; i--) { - for (j = grid_size - 1; j > 0; j--) { - if (grid[i][j] == grid[i][j - 1]) { - grid[i][j] <<= 1; - grid[i][j - 1] = 0; - score_last += grid[i][j]; - score += grid[i][j]; - moved = 1; - } - } - } - } - else if (d == DIR_DOWN) { - /* Move from lower right, across rows until first row which is skipped */ - for (i = grid_size - 1; i > 0; i--) { - for (j = grid_size - 1; j >= 0; j--) { - if (grid[i][j] == grid[i - 1][j]) { - grid[i][j] <<= 1; - grid[i - 1][j] = 0; - score_last += grid[i][j]; - score += grid[i][j]; - moved = 1; - } - } - } - } - - return moved; -} - - -/* move all values in the grid to the edge given by the direction pressed */ -/* would be nice to generalize this code a little so didn't need four loops */ -/* if animations are wanted, then need to alter this so it moves each square one at a time */ -int gravitate(dir_t d) -{ - int moved = 0; - int i, j; - - if (d == DIR_LEFT) { - for (i = 0; i < grid_size; i++) { - for (j = 0; j < grid_size - 1; j++) { - if (grid[i][j]) continue; - int st = 1; - while (j + st < grid_size && !grid[i][j + st]) st++; - if (j + st < grid_size) { - grid[i][j] = grid[i][j + st]; - grid[i][j + st] = 0; - moved = 1; - } - } - } - } - else if (d == DIR_UP) { - for (i = 0; i < grid_size; i++) { - for (j = 0; j < grid_size - 1; j++) { - if (grid[j][i]) continue; - int st = 1; - while (j + st < grid_size && !grid[j + st][i]) st++; - if (j + st < grid_size) { - grid[j][i] = grid[j + st][i]; - grid[j + st][i] = 0; - moved = 1; - } - } - } - } - else if (d == DIR_RIGHT) { - for (i = 0; i < grid_size; i++) { - for (j = grid_size - 1; j > 0; j--) { - if (grid[i][j]) continue; - int st = 1; - while (j - st >= 0 && !grid[i][j - st]) st++; - if (j - st >= 0) { - grid[i][j] = grid[i][j - st]; - grid[i][j - st] = 0; - moved = 1; - } - } - } - } - else if (d == DIR_DOWN) { - for (i = 0; i < grid_size; i++) { - for (j = grid_size - 1; j > 0; j--) { - if (grid[j][i]) continue; - int st = 1; - while (j - st >= 0 && !grid[j - st][i]) st++; - if (j - st >= 0) { - grid[j][i] = grid[j - st][i]; - grid[j - st][i] = 0; - moved = 1; - } - } - } - } - return moved; -} - -/* Return the current highscore */ -int get_score_high() { - int s = 0; - FILE *fd = fopen(HISCORE_FILE, "r"); - if (fd == NULL) - fd = fopen(HISCORE_FILE, "w+"); - - if (fscanf(fd, "%d", &s) == 1) {}; - fclose(fd); - return s; -} - -/* saves hiscore, but only if playing on standard size grid */ -void save_score_high() { - if (score > score_high && grid_size == 4) { - score_high = score; - FILE *fd = fopen(HISCORE_FILE, "w+"); - fprintf(fd, "%d", score_high); - fclose(fd); - } -} - -/* returns if there are any possible moves */ -int moves_available() -{ - int i, j; - for (i = 0; i < grid_size; i++) - for (j = 0; j < grid_size; j++) - if (!grid[i][j] - || ((i + 1 < grid_size) && (grid[i][j] == grid[i + 1][j])) - || ((j + 1 < grid_size) && (grid[i][j] == grid[i][j + 1]))) - return 1; - return 0; -} - -/* places a random block onto the grid - either a 4, or a 2 with a chance of 1:3 respectively */ -/* could do this in a much smarter fashion by finding which spaces are free */ -void rand_block() -{ - int x, y; - while (grid[x = rand() % grid_size][y = rand() % grid_size]); - grid[x][y] = (rand() & 3) ? 2 : 4; -} - -/* quick floor log2(n) */ -int flog2(unsigned int n) -{ - int k = 0; - while (n) - k++, n >>= 1; - return k; -} - -/* draws the grid and fills it with the current values */ -/* colors just rotate around, works for now, can be confusing when you have some fairly high values on the board */ -void draw_grid(WINDOW *gamewin) -{ - // mvwprintw will sometimes have a useless arg, this is warned, but doesn't affect the program - char *scr = score_last ? "SCORE: %d (+%d)\n" : "SCORE: %d\n"; - mvwprintw(gamewin, 0, 0, scr, score, score_last); - mvwprintw(gamewin, 1, 0, "HISCR: %d\n", score_high); - - ITER(grid_size*(printwidth + 2) + 1, waddch(gamewin, '-')); - int i, j, xps = 0, yps = 3; - for (i = 0; i < grid_size; i++, xps = 0, yps++) { - mvwprintw(gamewin, yps, xps++, "|"); - for (j = 0; j < grid_size; j++) { - if (grid[i][j]) { - wattron(gamewin, COLOR_PAIR(flog2(grid[i][j]) % 7)); - mvwprintw(gamewin, yps, xps, "%*d", printwidth, grid[i][j]); - wattroff(gamewin, COLOR_PAIR(flog2(grid[i][j]) % 7)); - mvwprintw(gamewin, yps, xps + printwidth, " |"); - } - else { - ITER(printwidth + 1, waddch(gamewin, ' ')); - waddch(gamewin, '|'); - } - xps += (printwidth + 2); - } - } - ITER(grid_size*(printwidth + 2) + 1, waddch(gamewin, '-')); - wrefresh(gamewin); -} - -/* entry point for the program */ -/* parses options and stores the main game loop */ -int main(int argc, char **argv) -{ - /* init ncurses environment */ - initscr(); - cbreak(); - noecho(); - curs_set(FALSE); - - /* init variables */ - score = 0; - score_last = 0; - score_high = get_score_high(); - grid_size = DEFAULT_GRID_SIZE; - printwidth = DEFAULT_GRID_SIZE; - - int n_blocks = 1; - - /* parse options */ - int c; - int enable_color = has_colors(); - while ((c = getopt(argc, argv, "rcChs:b:")) != -1) { - switch (c) { - /* Color support */ - case 'c': - enable_color = 1; - break; - case 'C': - enable_color = 0; - break; - // different board sizes - case 's':; - int optint = strtol(optarg, NULL, 10); - grid_size = optint > DEFAULT_GRID_SIZE ? optint : DEFAULT_GRID_SIZE; - break; - // different block spawn rate - case 'b': - n_blocks = strtol(optarg, NULL, 10); - break; - // reset hiscores - case 'r': - endwin(); - printf("Are you sure you want to reset your highscores? (Y)es or (N)o\n"); - int response; - if ((response = getchar()) == 'y' || response == 'Y') { - FILE *fd = fopen(HISCORE_FILE, "w+"); - fclose(fd); - } - exit(EXIT_SUCCESS); - // help menu - case 'h': - endwin(); - printf(USAGE_STR); - exit(EXIT_SUCCESS); - } - } - - if (enable_color) { - if (!has_colors()) { - fprintf(stderr, "Terminal does not support color\n"); - } else { - start_color(); - init_pair(0, 1, 0); - init_pair(1, 2, 0); - init_pair(2, 3, 0); - init_pair(3, 4, 0); - init_pair(4, 5, 0); - init_pair(5, 6, 0); - init_pair(6, 7, 0); - } - } - - /* Allocate memory once we actually know amount */ - CALLOC2D(grid, grid_size); - - int width = grid_size * (printwidth + 2) + 1; - int height = grid_size * (printwidth + 2) + 3; - - // might center in middle of screen - WINDOW *gamewin = newwin(height, width, 1, 1); - keypad(gamewin, TRUE); - - /* random seed */ - srand((unsigned int)time(NULL)); - ITER(2, rand_block()); - draw_grid(gamewin); - - int key, moved; - while (1) { - /* will goto this if we didn't get a valid keypress */ -retry:; - moved = 0; - score_last = 0; - key = wgetch(gamewin); - - /* should check if anything changed during merge and if not retry */ - switch (key) { - case 'h': - case 'a': - case KEY_LEFT: - moved = TURN(DIR_LEFT); - break; - case 'l': - case 'd': - case KEY_RIGHT: - moved = TURN(DIR_RIGHT); - break; - case 'j': - case 's': - case KEY_DOWN: - moved = TURN(DIR_DOWN); - break; - case 'k': - case 'w': - case KEY_UP: - moved = TURN(DIR_UP); - break; - case 'q': - FREE2D(grid, grid_size); - erase(); - refresh(); - endwin(); - save_score_high(); - exit(EXIT_SUCCESS); - default: - goto retry; - } - - if (!moves_available()) { - endwin(); - printf("\n" - "YOU LOSE! - Your score was %d\n", score); - save_score_high(); - exit(EXIT_SUCCESS); - } - - if (moved) { - ITER(n_blocks, rand_block()); - draw_grid(gamewin); - } - } - - return 0; -} diff --git a/src/2048.h b/src/2048.h deleted file mode 100644 index 49937c4..0000000 --- a/src/2048.h +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef _2048_H_ -#define _2048_H_ - -#include -#include -#include - -typedef enum { - DIR_UP, - DIR_RIGHT, - DIR_DOWN, - DIR_LEFT -} dir_t; - -#define DATADIR_NAME "2048" -#define HIGHSCORE_FILE_NAME "highscore" - -const char* get_highscore_file(); - -// Repeat an expression y, x times */ -#define ITER(x, expr)\ - do {\ - int i;\ - for (i = 0; i < (x); i++){ expr;}\ - } while (0) - -/* Allocates a square pointer of array of arrays onto heap */ -#define CALLOC2D(ptr, sz)\ - do {\ - int i;\ - ptr = calloc((sz), sizeof(*ptr));\ - for (i = 0; i < (sz); i++)\ - ptr[i] = calloc((sz), sizeof(*ptr));\ - } while (0) - -/* Frees a square pointer of arrays to arrays */ -#define FREE2D(ptr, sz)\ - do {\ - int i;\ - for (i = 0; i < (sz); i++)\ - free(ptr[i]);\ - free(ptr);\ - } while (0) - -/* What occurs during a 'turn' of execution */ -#define TURN(x) (gravitate(x) + merge(x) + gravitate(x)) - -/* Constants */ -#define DEFAULT_GRID_SIZE 4 -#define HISCORE_FILE get_highscore_file() -#define USAGE_STR\ - "Usage:\n"\ - " ./2048 [options]\n"\ - "\n"\ - "Controls:\n"\ - " hjkl, wasd Movement\n"\ - " q Quit\n"\ - "\n"\ - "Options:\n"\ - " -s Set the grid border length\n"\ - " -b Set the block spawn rate\n"\ - " -c Enables color support (ncurses version only)\n"\ - " -C Disables color support (ncurses version only)\n" - -#endif diff --git a/src/2048_no_curses.c b/src/2048_no_curses.c deleted file mode 100644 index ce281f8..0000000 --- a/src/2048_no_curses.c +++ /dev/null @@ -1,326 +0,0 @@ -/* - * 2048_no_curses.c - Non-curses version that can be updated at a later time - **/ - -#include /* for printf */ -#include /* for malloc */ -#include /* */ -#include /* for time */ -#include /* for getopts */ -#include "2048.h" - -int **g; /* grid pointer */ -int SZ; /* grid size */ -int s; /* Current score */ -int sl; /* Score for last turn */ -int hs; /* Highscore */ - -/* Merges adjacent squares of the same value together in a certain direction */ -int merge(int d) -{ - int moved = 0; - if (d == DIR_LEFT) { - int i, j; - for (i = 0; i < SZ; i++) { - for (j = 0; j < SZ; j++) { - if (j + 1 < SZ && g[i][j] && g[i][j] == g[i][j + 1]) { - g[i][j] <<= 1; - sl += g[i][j]; - s += g[i][j]; - g[i][j++ + 1] = 0; - moved = 1; - } - } - } - } - else if (d == DIR_UP) { - int i, j; - for (i = 0; i < SZ; i++) { - for (j = 0; j < SZ; j++) { - if (j + 1 < SZ && g[j][i] && g[j][i] == g[j + 1][i]) { - g[j][i] <<= 1; - sl += g[j][i]; - s += g[j][i]; - g[j++ + 1][i] = 0; - moved = 1; - } - } - } - } - else if (d == DIR_RIGHT) { - int i, j; - for (i = SZ - 1; i >= 0; i--) { - for (j = SZ - 1; j >= 0; j--) { - if (j > 0 && g[i][j] && g[i][j] == g[i][j - 1]) { - g[i][j] <<= 1; - sl += g[i][j]; - s += g[i][j]; - g[i][j-- - 1] = 0; - moved = 1; - } - } - } - } - else if (d == DIR_DOWN) { - int i, j; - for (i = SZ - 1; i >= 0; i--) { - for (j = SZ - 1; j >= 0; j--) { - if (j > 0 && g[j][i] && g[j][i] == g[j - 1][i]) { - g[j][i] <<= 1; - sl += g[j][i]; - s += g[j][i]; - g[j-- - 1][i] = 0; - moved = 1; - } - } - } - } - return moved; -} - - -/* move all values in the grid to the edge given by the direction pressed */ -/* would be nice to generalize this code a little so didn't need four loops */ -int gravitate(int d) -{ - int moved = 0; - if (d == DIR_LEFT) { - int i, j; - for (i = 0; i < SZ; i++) { - for (j = 0; j < SZ - 1; j++) { - if (g[i][j]) continue; - int st = 1; - while (j + st < SZ && !g[i][j + st]) st++; - if (j + st < SZ) { - g[i][j] = g[i][j + st]; - g[i][j + st] = 0; - moved = 1; - } - } - } - } - else if (d == DIR_UP) { - int i, j; - for (i = 0; i < SZ; i++) { - for (j = 0; j < SZ - 1; j++) { - if (g[j][i]) continue; - int st = 1; - while (j + st < SZ && !g[j + st][i]) st++; - if (j + st < SZ) { - g[j][i] = g[j + st][i]; - g[j + st][i] = 0; - moved = 1; - } - } - } - } - else if (d == DIR_RIGHT) { - int i, j; - for (i = 0; i < SZ; i++) { - for (j = SZ - 1; j > 0; j--) { - if (g[i][j]) continue; - int st = 1; - while (j - st >= 0 && !g[i][j - st]) st++; - if (j - st >= 0) { - g[i][j] = g[i][j - st]; - g[i][j - st] = 0; - moved = 1; - } - } - } - } - else if (d == DIR_DOWN) { - int i, j; - for (i = 0; i < SZ; i++) { - for (j = SZ - 1; j > 0; j--) { - if (g[j][i]) continue; - int st = 1; - while (j - st >= 0 && !g[j - st][i]) st++; - if (j - st >= 0) { - g[j][i] = g[j - st][i]; - g[j - st][i] = 0; - moved = 1; - } - } - } - } - return moved; -} - -/* loads hiscore */ -void load_score() { - FILE *fd = fopen(HISCORE_FILE, "r"); - if (fd == NULL) fd = fopen(HISCORE_FILE, "w+"); - if (fscanf(fd, "%d", &hs) == EOF) hs = 0; - fclose(fd); -} - -/* saves hiscore, but only if playing on standard size grid */ -void save_score() { - if (s > hs && SZ == 4) { - hs = s; - FILE *fd = fopen(HISCORE_FILE, "w+"); - fprintf(fd, "%d", hs); - fclose(fd); - } -} - -/* returns if there are any possible moves */ -int moves_available() -{ - int i, j; - for (i = 0; i < SZ; i++) - for (j = 0; j < SZ; j++) - if (!g[i][j] || ((i + 1 < SZ) && (g[i][j] == g[i + 1][j])) || ((j + 1 < SZ) && (g[i][j] == g[i][j + 1]))) - return 1; - return 0; -} - -/* places a random block onto the grid - either a 4, or a 2 with a ratio of 1:3 respectively */ -/* do this in a smarter fashion */ -void rand_block() -{ - int x_p, y_p; - while (g[x_p = rand() % SZ][y_p = rand() % SZ]); - g[x_p][y_p] = (rand() & 3) ? 2 : 4; -} - -/* draws the grid and fills it with the current values */ -void draw_grid() -{ - printf("HISCORE: %d |", hs); - printf("| SCORE: %d ", s); - if (sl) printf("(+%d)", sl); - printf("\n"); - - // alter this SZ + 1 to match abitrary grid size - ITER(SZ, printf("------")); - printf("-\n"); - int i, j; - for (i = 0; i < SZ; i++) { - printf("|"); - for (j = 0; j < SZ; j++) { - if (g[i][j]) - printf("%*d |", 4, g[i][j]); - else - printf(" |"); - } - printf("\n"); - } - ITER(SZ, printf("------")); - printf("-\n\n"); -} - -/* store the terminal settings and call this function on exit to restore */ -struct termios sattr; -void reset_term() -{ - tcsetattr(STDIN_FILENO, TCSANOW, &sattr); -} - -/* entry point for the program */ -/* parses options and stores the main game loop */ -int main(int argc, char **argv) -{ - - /* init variables */ - hs = 0; - s = 0; - sl = 0; - SZ = 4; - CALLOC2D(g, SZ); - - load_score(); - int n_blocks = 1; - - /* parse options */ - int c; - while ((c = getopt(argc, argv, "rhs:b:")) != -1) { - switch (c) { - // different board sizes - case 's': - FREE2D(g, SZ); - int optint = atoi(optarg); - SZ = optint > 4 ? optint : 4; - CALLOC2D(g, SZ); - break; - // different block spawn rate - case 'b': - n_blocks = atoi(optarg); - break; - // reset hiscores - case 'r': - printf("Are you sure you want to reset your highscores? (Y)es or (N)o\n"); - int response; - if ((response = getchar()) == 'y' || response == 'Y') { - FILE *fd = fopen(HISCORE_FILE, "w+"); - fclose(fd); - } - exit(EXIT_SUCCESS); - case 'h': - printf(USAGE_STR); - exit(EXIT_SUCCESS); - } - } - - /* random seed */ - srand((unsigned int)time(NULL)); - ITER(2, rand_block()); - draw_grid(); - - /* store term settings so we can restore on exit */ - tcgetattr(STDIN_FILENO, &sattr); - atexit(reset_term); - - /* 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); - - int key, moved; - while (1) { - /* will goto this if we didn't get a valid keypress */ - retry:; - moved = 0; - key = getchar(); - sl = 0; - - /* should check if anything changed during merge and if not retry */ - switch (key) { - case 'h': - case 'a': - moved = TURN(DIR_LEFT); - break; - case 'l': - case 'd': - moved = TURN(DIR_RIGHT); - break; - case 'j': - case 's': - moved = TURN(DIR_DOWN); - break; - case 'k': - case 'w': - moved = TURN(DIR_UP); - break; - case 'q': - save_score(); - exit(EXIT_SUCCESS); - default: - goto retry; - } - - if (!moves_available()) { - printf("\n" - "YOU LOSE! - Your score was %d\n", s); - save_score(); - exit(EXIT_SUCCESS); - } - - if (moved) { - ITER(n_blocks, rand_block()); - draw_grid(); - } - } -} diff --git a/src/highscore_file.c b/src/highscore_file.c deleted file mode 100644 index baeab33..0000000 --- a/src/highscore_file.c +++ /dev/null @@ -1,29 +0,0 @@ -#include "2048.h" - -#include -#include -#include -#include - -const char* get_highscore_file() { - static char buffer[4096]; - if (getenv("XDG_DATA_HOME") != NULL) { - snprintf(buffer, sizeof(buffer), "%s/%s/%s", getenv("XDG_DATA_HOME"), DATADIR_NAME, HIGHSCORE_FILE_NAME); - } else if (getenv("HOME") != NULL) { - snprintf(buffer, sizeof(buffer), "%s/.local/share/%s/%s", getenv("HOME"), DATADIR_NAME, HIGHSCORE_FILE_NAME); - } else { - return HIGHSCORE_FILE_NAME; - } - - // create hierarrchy of directories up to highscore file location - char* sep = strrchr(buffer, '/'); - while (sep != NULL) { - *sep = '\0'; - if (strlen(buffer) != 0) - mkdir(buffer, 0777); - char* tmpsep = sep; - sep = strrchr(buffer, '/'); - *tmpsep = '/'; - } - return buffer; -} From 3085bd4eb6122db433674add9e6323d394ea4ec4 Mon Sep 17 00:00:00 2001 From: Tiehuis Date: Sun, 7 Dec 2014 09:55:18 +1300 Subject: [PATCH 10/19] Removed unsigned/signed compare warnings. Change all to size_t types --- src/2048_engine.c | 38 +++++++++++++++++++------------------- src/2048_engine.h | 16 +++++++++++++++- src/2048_rewrite.c | 40 ++++++++++++++++++++++------------------ 3 files changed, 56 insertions(+), 38 deletions(-) diff --git a/src/2048_engine.c b/src/2048_engine.c index 2a93fe8..321879c 100644 --- a/src/2048_engine.c +++ b/src/2048_engine.c @@ -15,7 +15,7 @@ void gravitate(struct gamestate *g, direction d, void (*callback)(struct gamesta }\ } while (0) - int x, y; + size_t x, y; int done = 0; if (d == dir_left) { @@ -89,7 +89,7 @@ void merge(struct gamestate *g, direction d, void (*callback)(struct gamestate * }\ } while (0) - int x, y; + size_t x, y; g->score_last = 0; if (d == dir_left) { @@ -137,7 +137,7 @@ int end_condition(struct gamestate *g) { int ret = -1; - int x, y; + size_t 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] >= g->opts->goal) @@ -164,8 +164,8 @@ void random_block(struct gamestate *g) srand(time(NULL)); } - int x = rand() % g->opts->grid_width; - int y = rand() % g->opts->grid_height; + size_t x = (size_t)rand() % g->opts->grid_width; + size_t y = (size_t)rand() % g->opts->grid_height; while (g->grid[x][y]) { x++; @@ -183,7 +183,7 @@ static int flog10(unsigned int n) { int l = 0; while (n) n /= 10, ++l; - return l; + return l + 1; } struct gamestate* gamestate_init(struct gameoptions *opt) @@ -199,7 +199,7 @@ struct gamestate* gamestate_init(struct gameoptions *opt) g->grid = malloc(opt->grid_width * sizeof(long*)); if (!g->grid) goto grid_alloc_fail; - int i; + size_t i; for (i = 0; i < opt->grid_height; ++i) g->grid[i] = calloc(opt->grid_height, sizeof(long)); @@ -210,11 +210,10 @@ struct gamestate* gamestate_init(struct gameoptions *opt) g->print_width = flog10(opt->goal); g->opts = opt; - random_block(g); return g; grid_alloc_fail: -grid_back_alloc_fail: +//grid_back_alloc_fail: free(g); gamestate_alloc_fail: return NULL; @@ -225,13 +224,13 @@ 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; - opt->enable_color = 0; - opt->animate = 1; + opt->grid_height = DEFAULT_GRID_HEIGHT; + opt->grid_width = DEFAULT_GRID_WIDTH; + opt->goal = DEFAULT_GOAL; + opt->spawn_value = DEFAULT_SPAWN_VALUE; + opt->spawn_rate = DEFAULT_SPAWN_RATE; + opt->enable_color = DEFAULT_COLOR_TOGGLE; + opt->animate = DEFAULT_ANIMATE_TOGGLE; return opt; } @@ -242,7 +241,6 @@ int gamestate_tick(struct gamestate *g, direction d, void (*callback)(struct gam gravitate(g, d, callback); merge(g, d, callback); gravitate(g, d, callback); - random_block(g); return g->moved; } @@ -310,8 +308,10 @@ struct gameoptions* parse_options(struct gameoptions *opt, int argc, char **argv case 's':; /* Stick with square for now */ int optint = strtol(optarg, NULL, 10); - opt->grid_height = optint > 4 ? optint : 4; - opt->grid_width = optint > 4 ? optint : 4; + if (optint < CONSTRAINT_GRID_MAX && optint > CONSTRAINT_GRID_MIN) { + opt->grid_height = optint; + opt->grid_width = optint; + } break; case 'b': opt->spawn_rate = strtol(optarg, NULL, 10); diff --git a/src/2048_engine.h b/src/2048_engine.h index 55ba72b..57d7fe0 100644 --- a/src/2048_engine.h +++ b/src/2048_engine.h @@ -3,7 +3,21 @@ #include -#define fatal(msg) do { fprintf(stderr, "line %d: %s\n", __LINE__, msg); abort(); } while (0) +#define CONSTRAINT_GRID_MIN 4 +#define CONSTRAINT_GRID_MAX 20 +#define DEFAULT_GRID_HEIGHT 4 +#define DEFAULT_GRID_WIDTH 4 +#define DEFAULT_GOAL 2048 +#define DEFAULT_SPAWN_VALUE 2 +#define DEFAULT_SPAWN_RATE 1 +#define DEFAULT_COLOR_TOGGLE 0 +#define DEFAULT_ANIMATE_TOGGLE 1 + +#define fatal(msg)\ + do {\ + fprintf(stderr, "line %d: %s\n", __LINE__, msg);\ + abort();\ + } while (0) typedef enum { dir_left = 'h', diff --git a/src/2048_rewrite.c b/src/2048_rewrite.c index 80f3f60..f154217 100644 --- a/src/2048_rewrite.c +++ b/src/2048_rewrite.c @@ -8,7 +8,7 @@ #define iterate(x, expr)\ do {\ - int i;\ + size_t i;\ for (i = 0; i < x; ++i) { expr; }\ } while (0) @@ -26,13 +26,15 @@ void draw_screen(struct gamestate *g) 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); + if (g->score_last) + mvwprintw(gamewin, 0, 0, "SCORE: %d (+%d)\n", g->score, g->score_last); + else + mvwprintw(gamewin, 0, 0, "SCORE: %d\n", g->score); + mvwprintw(gamewin, 1, 0, "HISCR: %d\n", g->score_high); iterate(g->opts->grid_width*(g->print_width + 2) + 1, waddch(gamewin, '-')); - int x, y, xps = 0, yps = 3; + size_t x, y, xps = 0, yps = 3; for (y = 0; y < g->opts->grid_height; y++, xps = 0, yps++) { mvwprintw(gamewin, yps, xps++, "|"); for (x = 0; x < g->opts->grid_width; x++) { @@ -106,7 +108,7 @@ void draw_screen(struct gamestate *g) // alter this grid_size + 1 to match abitrary grid size iterate((g->print_width + 2) * g->opts->grid_width + 1, printf("-")); printf("\n"); - int x, y; + size_t x, y; for (y = 0; y < g->opts->grid_height; y++) { printf("|"); for (x = 0; x < g->opts->grid_width; x++) { @@ -131,22 +133,14 @@ void ddraw(struct gamestate *g) int main(int argc, char **argv) { - struct gamestate *g = gamestate_init( - parse_options( - gameoptions_default(), argc, argv)); + struct gameoptions *o = gameoptions_default(); + struct gamestate *g = gamestate_init(parse_options(o, argc, argv)); drawstate_init(); while (1) { draw_screen(g); - int e; - if ((e = end_condition(g))) { - drawstate_clear(); - printf(e > 0 ? "You win\n" : "You lose\n"); - goto endloop; - } - /* abstract getting keypress */ int ch; do { @@ -155,9 +149,19 @@ int main(int argc, char **argv) } while (strchr("hjkl", ch) == NULL); gamestate_tick(g, ch, g->opts->animate ? ddraw : NULL); - } -endloop: + int e; + if ((e = end_condition(g))) { + drawstate_clear(); + gamestate_clear(g); + printf(e > 0 ? "You win\n" : "You lose\n"); + goto endloop; + } + + random_block(g); + } + +endloop: drawstate_clear(); gamestate_clear(g); return 0; From 0a76b9c452446e69ef823f34d97684dca3e49688 Mon Sep 17 00:00:00 2001 From: Tiehuis Date: Sun, 7 Dec 2014 22:09:22 +1300 Subject: [PATCH 11/19] Various changes. Alter grid allocation. Add current block count --- src/2048_engine.c | 83 +++++++++++++++++++++++++++++++++++----------- src/2048_engine.h | 6 ++-- src/2048_rewrite.c | 37 +++++++++++++++++---- 3 files changed, 97 insertions(+), 29 deletions(-) diff --git a/src/2048_engine.c b/src/2048_engine.c index 321879c..eac7b1f 100644 --- a/src/2048_engine.c +++ b/src/2048_engine.c @@ -2,6 +2,9 @@ #include #include "2048_engine.h" +/* Utilize block counter to improve some of the functions so they can run + * quicker */ + void gravitate(struct gamestate *g, direction d, void (*callback)(struct gamestate *g)) { @@ -72,7 +75,6 @@ void gravitate(struct gamestate *g, direction d, void (*callback)(struct gamesta } #undef swap_if_space - } void merge(struct gamestate *g, direction d, void (*callback)(struct gamestate *g)) @@ -83,6 +85,7 @@ void merge(struct gamestate *g, direction d, void (*callback)(struct gamestate * 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->blocks_in_play -= 1;\ g->score_last += g->grid[x][y];\ g->score += g->grid[x][y];\ g->moved = 1;\ @@ -136,10 +139,18 @@ void merge(struct gamestate *g, direction d, void (*callback)(struct gamestate * int end_condition(struct gamestate *g) { int ret = -1; + size_t blocks_counted = 0; size_t 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]) { + blocks_counted++; + if (g->grid[x][y] >= g->opts->goal) + return 1; + else if (blocks_counted >= g->blocks_in_play) + return ret; + } if (g->grid[x][y] >= g->opts->goal) return 1; if (!g->grid[x][y] || ((x + 1 < g->opts->grid_width) && @@ -153,6 +164,9 @@ int end_condition(struct gamestate *g) return ret; } +/* Find a better method for getting a random square. It would be useful to keep + * track of how many blocks are in play, which we can query here, end_condition + * to improve them. */ void random_block(struct gamestate *g) { /* pick random square, if it is full, then move forward until we find @@ -164,22 +178,37 @@ void random_block(struct gamestate *g) srand(time(NULL)); } - size_t x = (size_t)rand() % g->opts->grid_width; - size_t y = (size_t)rand() % g->opts->grid_height; + /* Fix up this random number generator */ + /* Method: + * - Find a non-biased index between 0 and blocks_play, n + * - Find the nth 0 element in the array + * - insert a random value there + */ - while (g->grid[x][y]) { - x++; - if (x == g->opts->grid_width) { - x = 0; y++; - if (y == g->opts->grid_height) - y = 0; - } + /* Error here */ +#ifdef NULLO + size_t block_position = (size_t)rand() % ( + g->opts->grid_width * g->opts->grid_height - g->blocks_in_play); + + size_t i, ps; + for (i = 0, ps = 0; ps < block_position; ++i) { + if (!g->grid[i / g->opts->grid_width][i % g->opts->grid_height]) ps++; } + g->grid[i / g->opts->grid_width][i % g->opts->grid_height] + = (rand() & 3) ? g->opts->spawn_value : g->opts->spawn_value * 2; +#endif + + /* Use rudimentary for now */ + int x, y; + while (g->grid[x = rand() % g->opts->grid_width][y = rand() % g->opts->grid_height]); g->grid[x][y] = (rand() & 3) ? g->opts->spawn_value : g->opts->spawn_value * 2; + g->blocks_in_play += 1; } -static int flog10(unsigned int n) +/* This returns the number of digits in the base10 rep of n. The ceiling is + * taken so this will be one greater than required */ +static int clog10(unsigned int n) { int l = 0; while (n) n /= 10, ++l; @@ -192,28 +221,41 @@ struct gamestate* gamestate_init(struct gameoptions *opt) struct gamestate *g = malloc(sizeof(struct gamestate)); if (!g) goto gamestate_alloc_fail; + g->gridsize = opt->grid_width * opt->grid_height; - //long *grid_back = malloc(opt->grid_width * opt->grid_height * sizeof(long)); - //if (!grid_back) goto grid_back_alloc_fail; + long *grid_back = calloc(g->gridsize, sizeof(long)); + if (!grid_back) goto grid_back_alloc_fail; - g->grid = malloc(opt->grid_width * sizeof(long*)); + g->grid = malloc(opt->grid_height * sizeof(long*)); if (!g->grid) goto grid_alloc_fail; + /* Switch to two allocation version */ size_t i; - for (i = 0; i < opt->grid_height; ++i) - g->grid[i] = calloc(opt->grid_height, sizeof(long)); + long **iterator = g->grid; + for (i = 0; i < g->gridsize; i += opt->grid_width) + *iterator++ = &grid_back[i]; + + /* Switch back to the two allocs, setting pointers by iterating though */ + //size_t i; + //for (i = 0; i < opt->grid_height; ++i) + // g->grid[i] = calloc(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->print_width = clog10(opt->goal); + g->blocks_in_play = 0; g->opts = opt; + /* Initial 3 random blocks */ + random_block(g); + random_block(g); + random_block(g); return g; grid_alloc_fail: -//grid_back_alloc_fail: +grid_back_alloc_fail: free(g); gamestate_alloc_fail: return NULL; @@ -237,6 +279,7 @@ struct gameoptions* gameoptions_default(void) int gamestate_tick(struct gamestate *g, direction d, void (*callback)(struct gamestate*)) { + /* Reset move. Altered by gravitate and merge if we do move */ g->moved = 0; gravitate(g, d, callback); merge(g, d, callback); @@ -247,8 +290,8 @@ int gamestate_tick(struct gamestate *g, direction d, void (*callback)(struct gam void gamestate_clear(struct gamestate *g) { free(g->opts); - free(g->grid[0]); - free(g->grid); + free(g->grid[0]); /* Free grid data */ + free(g->grid); /* Free pointers to data slots */ free(g); } diff --git a/src/2048_engine.h b/src/2048_engine.h index 57d7fe0..c126f64 100644 --- a/src/2048_engine.h +++ b/src/2048_engine.h @@ -39,11 +39,13 @@ struct gameoptions { struct gamestate { /* Game state */ long **grid; + size_t gridsize; int moved; long score; long score_high; long score_last; - int print_width; + size_t print_width; + size_t blocks_in_play; /* Options */ struct gameoptions *opts; }; @@ -54,7 +56,7 @@ void merge(struct gamestate*, direction, void (*callback)(struct gamestate*)); int end_condition(struct gamestate *); void random_block(struct gamestate *); int gamestate_tick(struct gamestate*, direction, void (*callback)(struct gamestate*)); -void gamestate_clear(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 index f154217..a525c98 100644 --- a/src/2048_rewrite.c +++ b/src/2048_rewrite.c @@ -1,18 +1,34 @@ #include #include #include -#include -#include -#include #include "2048_engine.h" +/* Need to alter termios settings before actually cross-platform. */ +/* It will be something like this, maybe switch to using a time.h sleep + * method to get by os dependence, too? */ +#if (defined _WIN32 || defined __WIN64) +#include +#define syssleep(s) Sleep(s) +#elif defined __unix__ +#include +#define syssleep(s) usleep((s) * 1000) +#else +#error "Unsupported os: no sleep function" +#endif + +#ifdef HAVE_CURSES +#include +#else +#include +#endif + #define iterate(x, expr)\ do {\ size_t i;\ for (i = 0; i < x; ++i) { expr; }\ } while (0) -#ifdef HAVE_CURSES +#ifdef HAVE_CURSES /* PLATFORM/OUTPUT DEPENDENT */ void draw_screen(struct gamestate *g) { static WINDOW *gamewin; @@ -73,6 +89,9 @@ int get_keypress(void) } #else /* vt100 and standard shared functions */ + +/* Search for windows, blocking, non newline getchar */ +/* Could bypass termios that way */ struct termios sattr; void drawstate_clear() { @@ -83,13 +102,15 @@ 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); } +/* Need to check input queue, and remove all queued values on each getc to get + * by any caught chars during any sleeps which may occur. This is easy in the + * ncurses version (check until ERR), but requires extra tweaking here */ int get_keypress(void) { return fgetc(stdin); @@ -98,6 +119,8 @@ int get_keypress(void) void draw_screen(struct gamestate *g) { /* Clear the screen each draw if we are able to */ + + /* Check for windows clear screen */ #ifdef VT100_COMPATIBLE printf("\033[2J\033[H"); #endif @@ -123,12 +146,12 @@ void draw_screen(struct gamestate *g) iterate((g->print_width + 2) * g->opts->grid_width + 1, printf("-")); printf("\n\n"); } -#endif /* CURSES */ +#endif /* PLATFORM/OUTPUT DEPENDENT */ void ddraw(struct gamestate *g) { draw_screen(g); - usleep(40000); + syssleep(40); } int main(int argc, char **argv) From da0ff59971267f26bf363fd15f4624ea40d86b8a Mon Sep 17 00:00:00 2001 From: Marc Date: Thu, 11 Dec 2014 07:49:42 +1300 Subject: [PATCH 12/19] Makefile updated. Small init changes in game_engine --- .gitignore | 1 + Makefile | 16 ++++++++-------- src/2048_engine.c | 6 +----- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 8f5ca8b..6d260d6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /2048* +*.core diff --git a/Makefile b/Makefile index dc28d95..8fc3421 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,14 @@ CC ?= gcc CFLAGS += -Wall -Wextra -LIBS = -lcurses +DEFS = -DVT100_COMPATIBLE +LIBS = all: 2048 -2048: src/2048.c src/2048.h src/highscore_file.c - $(CC) $(CFLAGS) $(LDFLAGS) $(LIBS) src/2048.c src/highscore_file.c -o 2048 - -2048nc: src/2048_no_curses.c src/2048.h src/highscore_file.c - $(CC) $(CFLAGS) $(LDFLAGS) src/2048_no_curses.c src/highscore_file.c -o 2048nc - +2048: src/2048_engine.c src/2048_rewrite.c + $(CC) $(DEFS) src/2048_engine.c src/2048_rewrite.c -o 2048 $(LIBS) + clean: - rm -f 2048 2048nc + rm -f 2048 + +.PHONY: clean diff --git a/src/2048_engine.c b/src/2048_engine.c index eac7b1f..f0e7133 100644 --- a/src/2048_engine.c +++ b/src/2048_engine.c @@ -235,11 +235,6 @@ struct gamestate* gamestate_init(struct gameoptions *opt) for (i = 0; i < g->gridsize; i += opt->grid_width) *iterator++ = &grid_back[i]; - /* Switch back to the two allocs, setting pointers by iterating though */ - //size_t i; - //for (i = 0; i < opt->grid_height; ++i) - // g->grid[i] = calloc(opt->grid_height, sizeof(long)); - g->moved = 0; g->score = 0; g->score_high = 0; @@ -255,6 +250,7 @@ struct gamestate* gamestate_init(struct gameoptions *opt) return g; grid_alloc_fail: + free(grid_back); grid_back_alloc_fail: free(g); gamestate_alloc_fail: From 8cff1bb002cf838400906e3246cd68bc8790c3e3 Mon Sep 17 00:00:00 2001 From: Tiehuis Date: Sat, 21 Feb 2015 15:36:15 +1300 Subject: [PATCH 13/19] Major rewrite. Abstracting graphics code completely and allowing new extensions via a consistent drawing interface --- Makefile | 31 +++-- src/2048_engine.h | 62 ---------- src/2048_rewrite.c | 191 ----------------------------- src/{2048_engine.c => engine.c} | 207 ++++++++++---------------------- src/engine.h | 43 +++++++ src/gfx.h | 30 +++++ src/gfx_curses.c | 93 ++++++++++++++ src/gfx_terminal.c | 72 +++++++++++ src/main.c | 64 ++++++++++ src/options.c | 88 ++++++++++++++ src/options.h | 31 +++++ 11 files changed, 506 insertions(+), 406 deletions(-) delete mode 100644 src/2048_engine.h delete mode 100644 src/2048_rewrite.c rename src/{2048_engine.c => engine.c} (57%) create mode 100644 src/engine.h create mode 100644 src/gfx.h create mode 100644 src/gfx_curses.c create mode 100644 src/gfx_terminal.c create mode 100644 src/main.c create mode 100644 src/options.c create mode 100644 src/options.h diff --git a/Makefile b/Makefile index 8fc3421..c853a73 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,27 @@ -CC ?= gcc -CFLAGS += -Wall -Wextra -DEFS = -DVT100_COMPATIBLE -LIBS = +CC := clang +CFLAGS += -O2 -Wall -Wextra +LFLAGS += +DEFINES := -DVT100 -all: 2048 +PROGRAM := 2048 +C_FILES := $(wildcard src/*.c) +O_FILES := $(addprefix obj/,$(notdir $(C_FILES:.c=.o))) + +all: curses + +curses: $(O_FILES) + $(CC) $(filter-out obj/gfx%.o, $(O_FILES)) obj/gfx_curses.o -o $(PROGRAM) -lcurses + +vt100: $(O_FILES) + $(CC) $(filter-out obj/gfx%.o, $(O_FILES)) obj/gfx_terminal.o -o $(PROGRAM) + +obj/%.o: src/%.c + $(CC) $(DEFINES) $(CFLAGS) -c -o $@ $< + +remake: clean all -2048: src/2048_engine.c src/2048_rewrite.c - $(CC) $(DEFS) src/2048_engine.c src/2048_rewrite.c -o 2048 $(LIBS) - clean: + rm -f obj/* rm -f 2048 -.PHONY: clean +.PHONY: clean remake diff --git a/src/2048_engine.h b/src/2048_engine.h deleted file mode 100644 index c126f64..0000000 --- a/src/2048_engine.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef _2048_ENGINE -#define _2048_ENGINE - -#include - -#define CONSTRAINT_GRID_MIN 4 -#define CONSTRAINT_GRID_MAX 20 -#define DEFAULT_GRID_HEIGHT 4 -#define DEFAULT_GRID_WIDTH 4 -#define DEFAULT_GOAL 2048 -#define DEFAULT_SPAWN_VALUE 2 -#define DEFAULT_SPAWN_RATE 1 -#define DEFAULT_COLOR_TOGGLE 0 -#define DEFAULT_ANIMATE_TOGGLE 1 - -#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; - int enable_color; - int animate; -}; - -struct gamestate { - /* Game state */ - long **grid; - size_t gridsize; - int moved; - long score; - long score_high; - long score_last; - size_t print_width; - size_t blocks_in_play; - /* Options */ - struct gameoptions *opts; -}; - -struct gameoptions* parse_options(struct gameoptions*, int, char**); -void gravitate(struct gamestate*, direction, void (*callback)(struct gamestate*)); -void merge(struct gamestate*, direction, void (*callback)(struct gamestate*)); -int end_condition(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 deleted file mode 100644 index a525c98..0000000 --- a/src/2048_rewrite.c +++ /dev/null @@ -1,191 +0,0 @@ -#include -#include -#include -#include "2048_engine.h" - -/* Need to alter termios settings before actually cross-platform. */ -/* It will be something like this, maybe switch to using a time.h sleep - * method to get by os dependence, too? */ -#if (defined _WIN32 || defined __WIN64) -#include -#define syssleep(s) Sleep(s) -#elif defined __unix__ -#include -#define syssleep(s) usleep((s) * 1000) -#else -#error "Unsupported os: no sleep function" -#endif - -#ifdef HAVE_CURSES -#include -#else -#include -#endif - -#define iterate(x, expr)\ - do {\ - size_t i;\ - for (i = 0; i < x; ++i) { expr; }\ - } while (0) - -#ifdef HAVE_CURSES /* PLATFORM/OUTPUT DEPENDENT */ -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); - } - - if (g->score_last) - mvwprintw(gamewin, 0, 0, "SCORE: %d (+%d)\n", g->score, g->score_last); - else - mvwprintw(gamewin, 0, 0, "SCORE: %d\n", g->score); - - mvwprintw(gamewin, 1, 0, "HISCR: %d\n", g->score_high); - - iterate(g->opts->grid_width*(g->print_width + 2) + 1, waddch(gamewin, '-')); - size_t x, y, xps = 0, yps = 3; - for (y = 0; y < g->opts->grid_height; y++, xps = 0, yps++) { - mvwprintw(gamewin, yps, xps++, "|"); - for (x = 0; x < g->opts->grid_width; x++) { - if (g->grid[x][y]) { - mvwprintw(gamewin, yps, xps, "%*d", g->print_width, g->grid[x][y]); - mvwprintw(gamewin, yps, xps + g->print_width, " |"); - } - else { - iterate(g->print_width + 1, waddch(gamewin, ' ')); - waddch(gamewin, '|'); - } - xps += (g->print_width + 2); - } - } - iterate(g->opts->grid_height*(g->print_width + 2) + 1, waddch(gamewin, '-')); - wrefresh(gamewin); -} - -void drawstate_init(void) -{ - initscr(); - cbreak(); - noecho(); - curs_set(FALSE); - refresh(); -} - -void drawstate_clear(void) -{ - endwin(); -} - -int get_keypress(void) -{ - return getch(); -} - -#else /* vt100 and standard shared functions */ - -/* Search for windows, blocking, non newline getchar */ -/* Could bypass termios that way */ -struct termios sattr; -void drawstate_clear() -{ - tcsetattr(STDIN_FILENO, TCSANOW, &sattr); -} - -void drawstate_init(void) -{ - tcgetattr(STDIN_FILENO, &sattr); - - struct termios tattr; - tcgetattr(STDIN_FILENO, &tattr); - tattr.c_lflag &= ~(ICANON | ECHO); - tcsetattr(STDOUT_FILENO, TCSANOW, &tattr); -} - -/* Need to check input queue, and remove all queued values on each getc to get - * by any caught chars during any sleeps which may occur. This is easy in the - * ncurses version (check until ERR), but requires extra tweaking here */ -int get_keypress(void) -{ - return fgetc(stdin); -} - -void draw_screen(struct gamestate *g) -{ - /* Clear the screen each draw if we are able to */ - - /* Check for windows clear screen */ -#ifdef VT100_COMPATIBLE - printf("\033[2J\033[H"); -#endif - char *scr = g->score_last ? "SCORE: %d (+%d)\n" : "SCORE: %d\n"; - printf(scr, g->score, g->score_last); - printf("HISCR: %ld\n", g->score_high); - - // alter this grid_size + 1 to match abitrary grid size - iterate((g->print_width + 2) * g->opts->grid_width + 1, printf("-")); - printf("\n"); - size_t x, y; - for (y = 0; y < g->opts->grid_height; y++) { - printf("|"); - for (x = 0; x < g->opts->grid_width; x++) { - if (g->grid[x][y]) - printf("%*ld |", g->print_width, g->grid[x][y]); - else - printf("%*s |", g->print_width, ""); - } - printf("\n"); - } - - iterate((g->print_width + 2) * g->opts->grid_width + 1, printf("-")); - printf("\n\n"); -} -#endif /* PLATFORM/OUTPUT DEPENDENT */ - -void ddraw(struct gamestate *g) -{ - draw_screen(g); - syssleep(40); -} - -int main(int argc, char **argv) -{ - struct gameoptions *o = gameoptions_default(); - struct gamestate *g = gamestate_init(parse_options(o, argc, argv)); - - 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, g->opts->animate ? ddraw : NULL); - - int e; - if ((e = end_condition(g))) { - drawstate_clear(); - gamestate_clear(g); - printf(e > 0 ? "You win\n" : "You lose\n"); - goto endloop; - } - - random_block(g); - } - -endloop: - drawstate_clear(); - gamestate_clear(g); - return 0; -} diff --git a/src/2048_engine.c b/src/engine.c similarity index 57% rename from src/2048_engine.c rename to src/engine.c index f0e7133..9d4f715 100644 --- a/src/2048_engine.c +++ b/src/engine.c @@ -1,11 +1,14 @@ #include #include -#include "2048_engine.h" +#include "engine.h" /* Utilize block counter to improve some of the functions so they can run * quicker */ -void gravitate(struct gamestate *g, direction d, void (*callback)(struct gamestate *g)) +/* This function will move all blocks in the given game the given direction. + * The callback function is called after each single move. It can be used to + * animate the movement of the board. */ +static void gravitate(struct gfx_state *s, struct gamestate *g, int d, void (*callback)(struct gfx_state *s, struct gamestate *g)) { #define swap_if_space(xoff, yoff)\ @@ -18,7 +21,7 @@ void gravitate(struct gamestate *g, direction d, void (*callback)(struct gamesta }\ } while (0) - size_t x, y; + int x, y; int done = 0; if (d == dir_left) { @@ -30,7 +33,7 @@ void gravitate(struct gamestate *g, direction d, void (*callback)(struct gamesta } } if (callback) - callback(g); + callback(s, g); } } else if (d == dir_right) { @@ -42,7 +45,7 @@ void gravitate(struct gamestate *g, direction d, void (*callback)(struct gamesta } } if (callback) - callback(g); + callback(s, g); } } else if (d == dir_down) { @@ -54,7 +57,7 @@ void gravitate(struct gamestate *g, direction d, void (*callback)(struct gamesta } } if (callback) - callback(g); + callback(s, g); } } else if (d == dir_up) { @@ -66,7 +69,7 @@ void gravitate(struct gamestate *g, direction d, void (*callback)(struct gamesta } } if (callback) - callback(g); + callback(s, g); } } else { @@ -77,7 +80,16 @@ void gravitate(struct gamestate *g, direction d, void (*callback)(struct gamesta #undef swap_if_space } -void merge(struct gamestate *g, direction d, void (*callback)(struct gamestate *g)) +/* The merge function will combine adjacent blocks with the same value for + * the given direction. Note, left and right merges will merge in a different + * order, so they are not identical in all cases. + * + * Consider 2 2 2 0 + * + * Right merging: 4 0 2 0 + * Left merging: 2 0 4 0 + */ +static void merge(struct gfx_state *s, struct gamestate *g, int d, void (*callback)(struct gfx_state *s, struct gamestate *g)) { #define merge_if_equal(xoff, yoff)\ @@ -92,9 +104,9 @@ void merge(struct gamestate *g, direction d, void (*callback)(struct gamestate * }\ } while (0) - size_t x, y; + int x, y; g->score_last = 0; - + if (d == dir_left) { for (x = 0; x < g->opts->grid_width - 1; ++x) { for (y = 0; y < g->opts->grid_height; ++y) { @@ -129,33 +141,26 @@ void merge(struct gamestate *g, direction d, void (*callback)(struct gamestate * } if (callback) - callback(g); + callback(s, g); #undef merge_if_equal } -/* Return -1 on lose condition, 1 on win condition, 0 on - * haven't ended */ -int end_condition(struct gamestate *g) +/* Scan the current board and determine if an end outcome has been reached. + * -1 indicates a lose condition, 1 indicates a win condition, 0 indicates + * end has not yet been reached. */ +int gamestate_end_condition(struct gamestate *g) { int ret = -1; - size_t blocks_counted = 0; + //size_t blocks_counted = 0; - size_t 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]) { - blocks_counted++; - if (g->grid[x][y] >= g->opts->goal) - return 1; - else if (blocks_counted >= g->blocks_in_play) - return ret; - } + for (int x = 0; x < g->opts->grid_width; ++x) { + for (int y = 0; y < g->opts->grid_height; ++y) { if (g->grid[x][y] >= g->opts->goal) - return 1; + return 1; 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) && + || ((y + 1 < g->opts->grid_height) && (g->grid[x][y] == g->grid[x][y+1]))) ret = 0; } @@ -164,10 +169,8 @@ int end_condition(struct gamestate *g) return ret; } -/* Find a better method for getting a random square. It would be useful to keep - * track of how many blocks are in play, which we can query here, end_condition - * to improve them. */ -void random_block(struct gamestate *g) +/* Place a random block into the current grid */ +void gamestate_new_block(struct gamestate *g) { /* pick random square, if it is full, then move forward until we find * an empty square. This is biased */ @@ -179,14 +182,14 @@ void random_block(struct gamestate *g) } /* Fix up this random number generator */ - /* Method: + /* Method: * - Find a non-biased index between 0 and blocks_play, n * - Find the nth 0 element in the array * - insert a random value there */ /* Error here */ -#ifdef NULLO +#ifdef SKIP size_t block_position = (size_t)rand() % ( g->opts->grid_width * g->opts->grid_height - g->blocks_in_play); @@ -208,13 +211,17 @@ void random_block(struct gamestate *g) /* This returns the number of digits in the base10 rep of n. The ceiling is * taken so this will be one greater than required */ -static int clog10(unsigned int n) +static int digits_ceiling(unsigned int n) { int l = 0; while (n) n /= 10, ++l; return l + 1; } +/* Return NULL if we couldn't allocate space for the gamestate. The opt + * argument can be passed directly via gameoptions_default i.e + * *o = gamestate_init(gameoptions_default) is valid, as the delete function + * will find the pointer to the gameoptions and delete the data accordingly. */ struct gamestate* gamestate_init(struct gameoptions *opt) { if (!opt) return NULL; @@ -223,146 +230,58 @@ struct gamestate* gamestate_init(struct gameoptions *opt) if (!g) goto gamestate_alloc_fail; g->gridsize = opt->grid_width * opt->grid_height; - long *grid_back = calloc(g->gridsize, sizeof(long)); - if (!grid_back) goto grid_back_alloc_fail; - + g->grid_data_ptr = calloc(g->gridsize, sizeof(long)); + if (!g->grid_data_ptr) goto grid_data_alloc_fail; + g->grid = malloc(opt->grid_height * sizeof(long*)); if (!g->grid) goto grid_alloc_fail; /* Switch to two allocation version */ - size_t i; long **iterator = g->grid; - for (i = 0; i < g->gridsize; i += opt->grid_width) - *iterator++ = &grid_back[i]; + for (int i = 0; i < g->gridsize; i += opt->grid_width) + *iterator++ = &g->grid_data_ptr[i]; g->moved = 0; g->score = 0; g->score_high = 0; g->score_last = 0; - g->print_width = clog10(opt->goal); + g->print_width = digits_ceiling(opt->goal); g->blocks_in_play = 0; g->opts = opt; /* Initial 3 random blocks */ - random_block(g); - random_block(g); - random_block(g); + gamestate_new_block(g); + gamestate_new_block(g); + gamestate_new_block(g); return g; grid_alloc_fail: - free(grid_back); -grid_back_alloc_fail: + free(g->grid_data_ptr); +grid_data_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 = DEFAULT_GRID_HEIGHT; - opt->grid_width = DEFAULT_GRID_WIDTH; - opt->goal = DEFAULT_GOAL; - opt->spawn_value = DEFAULT_SPAWN_VALUE; - opt->spawn_rate = DEFAULT_SPAWN_RATE; - opt->enable_color = DEFAULT_COLOR_TOGGLE; - opt->animate = DEFAULT_ANIMATE_TOGGLE; - - return opt; -} - -int gamestate_tick(struct gamestate *g, direction d, void (*callback)(struct gamestate*)) +/* A tick is a gravitate, merge then gravitate all in the same direction. + * the moved variable is set to 0 initially and if the gravitate of merge + * functions modify it, we can determine which action to take. */ +int gamestate_tick(struct gfx_state *s, struct gamestate *g, int d, void (*callback)(struct gfx_state*, struct gamestate*)) { /* Reset move. Altered by gravitate and merge if we do move */ g->moved = 0; - gravitate(g, d, callback); - merge(g, d, callback); - gravitate(g, d, callback); + printf("%d\n", d); + gravitate(s, g, d, callback); + merge(s, g, d, callback); + gravitate(s, g, d, callback); return g->moved; } +/* Free all data associated with the gamestate */ void gamestate_clear(struct gamestate *g) { - free(g->opts); - free(g->grid[0]); /* Free grid data */ + gameoptions_destroy(g->opts); + free(g->grid_data_ptr); /* Free grid data */ free(g->grid); /* Free pointers to data slots */ free(g); } - -/* The following may be moved into own file */ -void reset_highscore(void) -{ - printf("Are you sure you want to reset your highscores? (Y)es/(N)o: "); - - int response; - if ((response = getchar()) == 'y' || response == 'Y') { - printf("Resetting highscore...\n"); - } -} - -void print_usage(void) -{ - printf( - "usage: 2048 [-cCaArh] [-g ] [-b ] [-s ]\n" - "\n" - "controls\n" - " hjkl movement keys\n" - " q quit current game\n" - "\n" - "options\n" - " -s set the grid side lengths\n" - " -b set the block spawn rate\n" - " -g set a new goal (default 2048)\n" - " -a enable animations (default)\n" - " -A disable animations\n" - " -c enable color support\n" - " -C disable color support (default)\n" - ); -} - -#include - -struct gameoptions* parse_options(struct gameoptions *opt, int argc, char **argv) -{ - int c; - while ((c = getopt(argc, argv, "aArcChg:s:b:")) != -1) { - switch (c) { - case 'a': - opt->animate = 1; - break; - case 'A': - opt->animate = 0; - break; - case 'c': - opt->enable_color = 1; - break; - case 'C': - opt->enable_color = 0; - break; - case 'g': - opt->goal = strtol(optarg, NULL, 10); - break; - case 's':; - /* Stick with square for now */ - int optint = strtol(optarg, NULL, 10); - if (optint < CONSTRAINT_GRID_MAX && optint > CONSTRAINT_GRID_MIN) { - opt->grid_height = optint; - opt->grid_width = optint; - } - break; - case 'b': - opt->spawn_rate = strtol(optarg, NULL, 10); - break; - case 'r': - reset_highscore(); - exit(0); - case 'h': - print_usage(); - exit(0); - } - } - - return opt; -} diff --git a/src/engine.h b/src/engine.h new file mode 100644 index 0000000..0e90f07 --- /dev/null +++ b/src/engine.h @@ -0,0 +1,43 @@ +#ifndef ENGINE_H +#define ENGINE_H + +#include +#include "gfx.h" +#include "options.h" + +#define fatal(msg)\ + do {\ + fprintf(stderr, "line %d: %s\n", __LINE__, msg);\ + abort();\ + } while (0) + +struct gamestate { + /* Game state */ + long *grid_data_ptr; + long **grid; + int gridsize; + int moved; + long score; + long score_high; + long score_last; + int print_width; + int blocks_in_play; + /* Variable command line options */ + struct gameoptions *opts; +}; + +enum { + dir_invalid, + dir_down, + dir_left, + dir_right, + dir_up +}; + +int gamestate_end_condition(struct gamestate*); +void gamestate_new_block(struct gamestate*); +int gamestate_tick(struct gfx_state*, struct gamestate*, int, void (*callback)(struct gfx_state*, struct gamestate*)); +void gamestate_clear(struct gamestate*); +struct gamestate* gamestate_init(struct gameoptions *); + +#endif diff --git a/src/gfx.h b/src/gfx.h new file mode 100644 index 0000000..06f88fa --- /dev/null +++ b/src/gfx.h @@ -0,0 +1,30 @@ +#ifndef GFX_H +#define GFX_H + +#include "engine.h" + +/* These can be defined by an implemenation if special codes are used + * to represent some keys. e.g. KEY_LEFT in ncurses */ +#define GFX_RIGHT_EXTRA +#define GFX_LEFT_EXTRA +#define GFX_DOWN_EXTRA +#define GFX_UP_EXTRA + +struct gfx_state; + +/* Initialization of a graphics context */ +struct gfx_state* gfx_init(struct gamestate *); + +/* Drawing of a game_state onto a graphics context */ +void gfx_draw(struct gfx_state *, struct gamestate *); + +/* Blocking get character. Should not be buffered for best results */ +int gfx_getch(struct gfx_state *); + +/* Destruction of a graphics context */ +void gfx_destroy(struct gfx_state *); + +/* Sleep for a specifed millisecond period */ +void gfx_sleep(int ms); + +#endif diff --git a/src/gfx_curses.c b/src/gfx_curses.c new file mode 100644 index 0000000..ee6fa8a --- /dev/null +++ b/src/gfx_curses.c @@ -0,0 +1,93 @@ +#include +#include +#include +#include "gfx.h" + +#define GFX_EXTRA_UP case KEY_UP: +#define GFX_EXTRA_DOWN case KEY_DOWN: +#define GFX_EXTRA_RIGHT case KEY_RIGHT: +#define GFX_EXTRA_LEFT case KEY_LEFT: + +#define iterate(n, expression)\ + do {\ + int i;\ + for (i = 0; i < n; ++i) { expression; }\ + } while (0) + +struct gfx_state { + WINDOW *window; + size_t window_height, window_width; +}; + +struct gfx_state* gfx_init(struct gamestate *g) +{ + initscr(); + cbreak(); + noecho(); + curs_set(FALSE); + refresh(); + + struct gfx_state *s = malloc(sizeof(struct gfx_state)); + s->window_height = g->opts->grid_height * (g->print_width + 2) + 3; + s->window_width = g->opts->grid_width * (g->print_width + 2) + 1; + s->window = newwin(s->window_height, s->window_width, 1, 1); + keypad(s->window, TRUE); + return s; +} + +void gfx_draw(struct gfx_state *s, struct gamestate *g) +{ + if (g->score_last) + mvwprintw(s->window, 0, 0, "Score: %d (+%d)\n", g->score, g->score_last); + else + mvwprintw(s->window, 0, 0, "Score: %d\n", g->score); + + mvwprintw(s->window, 1, 0, " Hi: %d\n", g->score_high); + iterate(g->opts->grid_width * (g->print_width + 2) + 1, waddch(s->window, '-')); + + int x, y, + xpos = 0, + ypos = 3; + + for (y = 0; y < g->opts->grid_height; ++y, ++ypos, xpos = 0) { + mvwprintw(s->window, ypos, xpos++, "|"); + + for (x = 0; x < g->opts->grid_width; ++x) { + if (g->grid[x][y]) { + mvwprintw(s->window, ypos, xpos, "%*d", g->print_width, g->grid[x][y]); + mvwprintw(s->window, ypos, xpos + g->print_width, " |"); + } + else { + iterate(g->print_width + 1, waddch(s->window, ' ')); + waddch(s->window, '|'); + } + + xpos += (g->print_width + 2); + } + } + + iterate(g->opts->grid_height * (g->print_width + 2) + 1, waddch(s->window, '-')); + wrefresh(s->window); +} + +int gfx_getch(struct gfx_state *s) +{ + int c = wgetch(s->window); + + /* Flush buffer */ + nodelay(s->window, TRUE); + while (wgetch(s->window) != ERR); + nodelay(s->window, FALSE); + return c; +} + +void gfx_sleep(int ms) +{ + usleep(ms * 1000); +} + +void gfx_destroy(struct gfx_state *s) +{ + free(s); + endwin(); +} diff --git a/src/gfx_terminal.c b/src/gfx_terminal.c new file mode 100644 index 0000000..cad79e5 --- /dev/null +++ b/src/gfx_terminal.c @@ -0,0 +1,72 @@ +#include +#include +#include +#include +#include "gfx.h" + +#define iterate(n, expression)\ + do {\ + int i;\ + for (i = 0; i < n; ++i) { expression; }\ + } while (0) + +struct gfx_state { + struct termios oldt, newt; +}; + +struct gfx_state* gfx_init(struct gamestate *g) +{ + struct gfx_state *s = malloc(sizeof(struct gfx_state)); + tcgetattr(STDIN_FILENO, &s->oldt); + s->newt = s->oldt; + s->newt.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &s->newt); + return s; +} + +void gfx_draw(struct gfx_state *s, struct gamestate *g) +{ +#ifdef VT100 + printf("\033[2J\033[H"); +#endif + + if (g->score_last) + printf("Score: %ld (+%ld)\n", g->score, g->score_last); + else + printf("Score: %ld\n", g->score); + + printf(" Hi: %ld\n", g->score_high); + + iterate((g->print_width + 2) * g->opts->grid_width + 1, printf("-")); printf("\n"); + + int x, y; + for (y = 0; y < g->opts->grid_width; ++y) { + printf("|"); + + for (x = 0; x < g->opts->grid_width; ++x) { + if (g->grid[x][y]) + printf("%*zd |", g->print_width, g->grid[x][y]); + else + printf("%*s |", g->print_width, ""); + } + printf("\n"); + } + + iterate((g->print_width + 2) * g->opts->grid_width + 1, printf("-")); printf("\n\n"); +} + +int gfx_getch(struct gfx_state *s) +{ + return getchar(); +} + +void gfx_sleep(int ms) +{ + usleep(ms * 1000); +} + +void gfx_destroy(struct gfx_state *t) +{ + tcsetattr(STDIN_FILENO, TCSANOW, &t->oldt); + free(t); +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..f683559 --- /dev/null +++ b/src/main.c @@ -0,0 +1,64 @@ +#include +#include +#include "engine.h" +#include "gfx.h" + +void draw_then_sleep(struct gfx_state *s, struct gamestate *g) +{ + gfx_draw(s, g); + gfx_sleep(40); +} + +int main(int argc, char **argv) +{ + struct gameoptions *o = gameoptions_default(); + struct gamestate *g = gamestate_init(parse_options(o, argc, argv)); + struct gfx_state *s = gfx_init(g); + + int game_running = true; + while (game_running) { + gfx_draw(s, g); + +get_new_key:; + int direction = dir_invalid; + switch (gfx_getch(s)) { + case 'h': + case 'a': + direction = dir_left; + break; + case 'l': + case 'd': + direction = dir_right; + break; + case 'j': + case 's': + direction = dir_down; + break; + case 'k': + case 'w': + direction = dir_up; + break; + case 'q': + game_running = false; + break; + default: + goto get_new_key; + } + + /* Game will only end if 0 moves available */ + if (game_running) { + if (gamestate_tick(s, g, direction, g->opts->animate ? draw_then_sleep : NULL)) + gamestate_new_block(g); + + if (gamestate_end_condition(g)) { + game_running = false; + goto game_end; + } + } + } + +game_end: + gfx_destroy(s); + gamestate_clear(g); + return 0; +} diff --git a/src/options.c b/src/options.c new file mode 100644 index 0000000..a734e01 --- /dev/null +++ b/src/options.c @@ -0,0 +1,88 @@ +#include +#include +#include "options.h" + +void print_usage(void) +{ + printf( + "usage: 2048 [-cCaArh] [-g ] [-b ] [-s ]\n" + "\n" + "controls\n" + " hjkl movement keys\n" + " q quit current game\n" + "\n" + "options\n" + " -s set the grid side lengths\n" + " -b set the block spawn rate\n" + " -g set a new goal (default 2048)\n" + " -a enable animations (default)\n" + " -A disable animations\n" + " -c enable color support\n" + " -C disable color support (default)\n" + ); +} + + +/* Initial game options */ +struct gameoptions* gameoptions_default(void) +{ + struct gameoptions *opt = malloc(sizeof(struct gameoptions)); + if (!opt) return NULL; + + opt->grid_height = DEFAULT_GRID_HEIGHT; + opt->grid_width = DEFAULT_GRID_WIDTH; + opt->goal = DEFAULT_GOAL; + opt->spawn_value = DEFAULT_SPAWN_VALUE; + opt->spawn_rate = DEFAULT_SPAWN_RATE; + opt->enable_color = DEFAULT_COLOR_TOGGLE; + opt->animate = DEFAULT_ANIMATE_TOGGLE; + + return opt; +} + +void gameoptions_destroy(struct gameoptions *opt) +{ + free(opt); +} + +struct gameoptions* parse_options(struct gameoptions *opt, int argc, char **argv) +{ + int c; + while ((c = getopt(argc, argv, "aArcChg:s:b:")) != -1) { + switch (c) { + case 'a': + opt->animate = 1; + break; + case 'A': + opt->animate = 0; + break; + case 'c': + opt->enable_color = 1; + break; + case 'C': + opt->enable_color = 0; + break; + case 'g': + opt->goal = strtol(optarg, NULL, 10); + break; + case 's':; + /* Stick with square for now */ + int optint = strtol(optarg, NULL, 10); + if (optint < CONSTRAINT_GRID_MAX && optint > CONSTRAINT_GRID_MIN) { + opt->grid_height = optint; + opt->grid_width = optint; + } + break; + case 'b': + opt->spawn_rate = strtol(optarg, NULL, 10); + break; + case 'r': + exit(0); + case 'h': + print_usage(); + exit(0); + } + } + + return opt; +} diff --git a/src/options.h b/src/options.h new file mode 100644 index 0000000..3b80f64 --- /dev/null +++ b/src/options.h @@ -0,0 +1,31 @@ +#ifndef OPTIONS_H +#define OPTIONS_H + +#include + +#define CONSTRAINT_GRID_MIN 4 +#define CONSTRAINT_GRID_MAX 20 +#define DEFAULT_GRID_HEIGHT 4 +#define DEFAULT_GRID_WIDTH 4 +#define DEFAULT_GOAL 2048 +#define DEFAULT_SPAWN_VALUE 2 +#define DEFAULT_SPAWN_RATE 1 +#define DEFAULT_COLOR_TOGGLE 0 +#define DEFAULT_ANIMATE_TOGGLE 1 + +struct gameoptions { + int grid_height; + int grid_width; + long goal; + long spawn_value; + int spawn_rate; + int enable_color; + int animate; +}; + +void print_usage(void); +struct gameoptions* parse_options(struct gameoptions*, int, char**); +struct gameoptions* gameoptions_default(void); +void gameoptions_destroy(struct gameoptions *opt); + +#endif From 3dd23136d50403dc0ba884d4bc30bd78c8833532 Mon Sep 17 00:00:00 2001 From: Tiehuis Date: Sat, 21 Feb 2015 17:31:54 +1300 Subject: [PATCH 14/19] Added sdl version to exmplify extension of the main engine --- Makefile | 7 +- README.md | 25 +++--- res/Anonymous Pro.ttf | Bin 0 -> 158080 bytes res/NotCourierSans.ttf | Bin 0 -> 69600 bytes src/engine.c | 1 - src/gfx_sdl.c | 181 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 200 insertions(+), 14 deletions(-) create mode 100644 res/Anonymous Pro.ttf create mode 100644 res/NotCourierSans.ttf create mode 100644 src/gfx_sdl.c diff --git a/Makefile b/Makefile index c853a73..62ed8cd 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ CC := clang -CFLAGS += -O2 -Wall -Wextra +CFLAGS += -g -Wall -Wextra LFLAGS += -DEFINES := -DVT100 +DEFINES := -DVT100 -D_REENTRANT -I/usr/include/SDL2 PROGRAM := 2048 C_FILES := $(wildcard src/*.c) @@ -15,6 +15,9 @@ curses: $(O_FILES) vt100: $(O_FILES) $(CC) $(filter-out obj/gfx%.o, $(O_FILES)) obj/gfx_terminal.o -o $(PROGRAM) +sdl: $(O_FILES) + $(CC) $(filter-out obj/gfx%.o, $(O_FILES)) obj/gfx_sdl.o -o $(PROGRAM) -lSDL2 -lSDL2_ttf + obj/%.o: src/%.c $(CC) $(DEFINES) $(CFLAGS) -c -o $@ $< diff --git a/README.md b/README.md index 71e3392..1621d4a 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,25 @@ #2048-cli -A cli version of the game [2048](https://github.com/gabrielecirulli/2048) for your Linux +A cli version/engine of the game [2048](https://github.com/gabrielecirulli/2048) for your Linux terminal. -#####2048_curses.c ![Screenshot](http://i.imgur.com/QU7t5mH.png) -#####2048_no_curses.c -![Screenshot](http://i.imgur.com/fwZEvdh.png) - -## Installation -This requires the ncurses library to link against during compilation. It is available -in most package managers. The program creates and uses a file name `.hs2048g` in the -working directory. Any file with this name will be modified and replaced. +There currently are 3 versions that can be run. These include a straight-forward terminal +based, and two using the ncurses and SDL libraries. To add a new graphical version, simply +create a .c file which implements all the functions in gfx.h and add a Makefile entry. ### Get git clone https://github.com/Tiehuis/2048-cli.git - make + + make vt100 +or + make curses +or + make sdl + ### Run - ./2048 + ./2048 ## Options -s Set the grid border length @@ -27,5 +28,7 @@ working directory. Any file with this name will be modified and replaced. -c Enables color support (ncurses version only) -C Disables color support (ncurses version only) +Fonts used in SDL version can be found [here](openfontlibrary.org). + ## License This code is licensed under the [MIT License](https://github.com/Tiehuis/2048-cli/blob/master/LICENSE). diff --git a/res/Anonymous Pro.ttf b/res/Anonymous Pro.ttf new file mode 100644 index 0000000000000000000000000000000000000000..06aafc067310fba0d33b0a133654475ac73388ab GIT binary patch literal 158080 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

~51kCJv>^F~QVhnxG5_ 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

hJ4kotiQ8)XW*D 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 Od;JCaeD&GF_x}%-KB#5@ literal 0 HcmV?d00001 diff --git a/src/engine.c b/src/engine.c index 9d4f715..ae2192d 100644 --- a/src/engine.c +++ b/src/engine.c @@ -270,7 +270,6 @@ int gamestate_tick(struct gfx_state *s, struct gamestate *g, int d, void (*callb { /* Reset move. Altered by gravitate and merge if we do move */ g->moved = 0; - printf("%d\n", d); gravitate(s, g, d, callback); merge(s, g, d, callback); gravitate(s, g, d, callback); diff --git a/src/gfx_sdl.c b/src/gfx_sdl.c new file mode 100644 index 0000000..c3b11cd --- /dev/null +++ b/src/gfx_sdl.c @@ -0,0 +1,181 @@ +#include +#include +#include +#include "gfx.h" + +/* Side length of a 'pixel' in pixels */ +#define TTF_FONT_PATH "res/Anonymous Pro.ttf" +#define TTF_FONT_PT 32 + +#define iterate(n, expression)\ + do {\ + int i;\ + for (i = 0; i < n; ++i) { expression; }\ + } while (0) + +struct gfx_state { + SDL_Window *window; + SDL_Surface *surface; + TTF_Font *font; + int side_length; + int window_height; + int window_width; +}; + +struct gfx_state* gfx_init(struct gamestate *g) +{ + struct gfx_state *s = malloc(sizeof(struct gfx_state)); + + SDL_Init(SDL_INIT_VIDEO); + TTF_Init(); + s->font = TTF_OpenFont(TTF_FONT_PATH, TTF_FONT_PT); + + s->side_length = TTF_FontLineSkip(s->font); + s->window_height = g->opts->grid_height * (g->print_width + 2) + 3; + s->window_width = g->opts->grid_width * (g->print_width + 2) + 1; + s->window = SDL_CreateWindow( + "2048", + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + + /* Spacing is inconsistent right now. Need to find width and + * height spacing to accurately resize window */ + s->window_width * TTF_FONT_PT * 0.7, + s->window_height * s->side_length * 0.35, + SDL_WINDOW_OPENGL | SDL_WINDOW_ALLOW_HIGHDPI + ); + + s->surface = SDL_GetWindowSurface(s->window); + + return s; +} + +#define rect_set(r, xv, yv, wv, hv)\ + do {\ + r.x = xv; r.y = yv; r.w = wv; r.h = hv;\ + } while (0) + + +void gfx_draw(struct gfx_state *s, struct gamestate *g) +{ + /* This shouldn't ever overflow. Max width effectively is determined by the size + * of two integers' text representation */ + const int buffer_length = 64; + char string_buffer[buffer_length]; + + /* Set up text object so we can write to sdl window */ + SDL_Surface *text; + SDL_Color text_color = {255, 255, 255, 0}; + SDL_Rect rect; + + /* Clear screen */ + SDL_FillRect(s->surface, NULL, SDL_MapRGB(s->surface->format, 0, 0, 0)); + + if (g->score_last) + snprintf(string_buffer, buffer_length, "Score: %ld (+%ld)", g->score, g->score_last); + else + snprintf(string_buffer, buffer_length, "Score: %ld", g->score); + + rect_set(rect, 0, 0, 0, 0); + text = TTF_RenderText_Solid(s->font, string_buffer, text_color); + SDL_BlitSurface(text, NULL, s->surface, &rect); + + snprintf(string_buffer, buffer_length, " Hi: %ld", g->score_high); + rect_set(rect, 0, s->side_length * 1, 0, 0); + text = TTF_RenderText_Solid(s->font, string_buffer, text_color); + SDL_BlitSurface(text, NULL, s->surface, &rect); + + int i; + for (i = 0; i < g->opts->grid_width * (g->print_width + 2) + 1; ++i) + string_buffer[i] = '-'; + string_buffer[i] = '\0'; + + rect_set(rect, 0, s->side_length * 2, 0, 0); + text = TTF_RenderText_Solid(s->font, string_buffer, text_color); + SDL_BlitSurface(text, NULL, s->surface, &rect); + + int x, y; + char line_buffer[buffer_length]; + for (y = 0; y < g->opts->grid_height; ++y) { + int line_index = 0; + line_buffer[line_index++] = '|'; + + for (x = 0; x < g->opts->grid_width; ++x) { + if (g->grid[x][y]) { + snprintf(string_buffer, buffer_length, "%*ld |", g->print_width, g->grid[x][y]); + strncpy(line_buffer + line_index, string_buffer, buffer_length - line_index); + line_index += strlen(string_buffer); + } + else { + snprintf(string_buffer, buffer_length, "%*s |", g->print_width, ""); + strncpy(line_buffer + line_index, string_buffer, buffer_length - line_index); + line_index += strlen(string_buffer); + } + } + + line_buffer[line_index] = 0; + rect_set(rect, 0, s->side_length * (y + 3), 0, 0); + text = TTF_RenderText_Solid(s->font, line_buffer, text_color); + SDL_BlitSurface(text, NULL, s->surface, &rect); + } + + for (i = 0; i < g->opts->grid_height * (g->print_width + 2) + 1; ++i) + string_buffer[i] = '-'; + string_buffer[i] = '\0'; + + rect_set(rect, 0, s->side_length * (y + 3), 0, 0); + text = TTF_RenderText_Solid(s->font, string_buffer, text_color); + SDL_BlitSurface(text, NULL, s->surface, &rect); + SDL_UpdateWindowSurface(s->window); +} + +/* This getch we parse here, and we just return when we get an appropriate + * event */ +int gfx_getch(struct gfx_state *s) +{ + (void)s; /* Supress unused warning */ + SDL_Event event; + + while (1) { + while (SDL_WaitEvent(&event)) { + switch (event.type) { + case SDL_QUIT: + return 'q'; + case SDL_KEYDOWN: + switch (event.key.keysym.sym) { + case SDLK_q: + return 'q'; + case SDLK_UP: + case SDLK_w: + return 'w'; + case SDLK_DOWN: + case SDLK_s: + return 's'; + case SDLK_LEFT: + case SDLK_a: + return 'a'; + case SDLK_RIGHT: + case SDLK_d: + return 'd'; + default: + break; + } + break; + default: + break; + } + } + } +} + +void gfx_sleep(int ms) +{ + SDL_Delay(ms); +} + +void gfx_destroy(struct gfx_state *s) +{ + SDL_DestroyWindow(s->window); + TTF_Quit(); + SDL_Quit(); +} From 75ef085260b6ae6f9284303d17f1a869c46cc524 Mon Sep 17 00:00:00 2001 From: Tiehuis Date: Sat, 21 Feb 2015 17:37:54 +1300 Subject: [PATCH 15/19] Small makefile fixes and readme alterations --- Makefile | 2 +- README.md | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 62ed8cd..0807672 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ CC := clang CFLAGS += -g -Wall -Wextra LFLAGS += -DEFINES := -DVT100 -D_REENTRANT -I/usr/include/SDL2 +DEFINES := -DVT100 $(shell pkg-config --cflags sdl2) PROGRAM := 2048 C_FILES := $(wildcard src/*.c) diff --git a/README.md b/README.md index 1621d4a..a30244c 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,7 @@ create a .c file which implements all the functions in gfx.h and add a Makefile ### Get git clone https://github.com/Tiehuis/2048-cli.git - - make vt100 -or - make curses -or - make sdl + make ### Run ./2048 From c8e55ed6e9146e8fd22cfc4efef36c7424e150eb Mon Sep 17 00:00:00 2001 From: Tiehuis Date: Sun, 22 Feb 2015 11:29:15 +1300 Subject: [PATCH 16/19] Added highscore functionality --- README.md | 2 +- src/engine.c | 4 +++ src/highscore.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++++ src/highscore.h | 8 ++++++ src/options.c | 2 ++ 5 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 src/highscore.c create mode 100644 src/highscore.h diff --git a/README.md b/README.md index a30244c..c04b6e3 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ create a .c file which implements all the functions in gfx.h and add a Makefile -c Enables color support (ncurses version only) -C Disables color support (ncurses version only) -Fonts used in SDL version can be found [here](openfontlibrary.org). +Fonts used in SDL version can be found [here](www.openfontlibrary.org). ## License This code is licensed under the [MIT License](https://github.com/Tiehuis/2048-cli/blob/master/LICENSE). diff --git a/src/engine.c b/src/engine.c index ae2192d..cb10a00 100644 --- a/src/engine.c +++ b/src/engine.c @@ -1,6 +1,7 @@ #include #include #include "engine.h" +#include "highscore.h" /* Utilize block counter to improve some of the functions so they can run * quicker */ @@ -249,6 +250,8 @@ struct gamestate* gamestate_init(struct gameoptions *opt) g->blocks_in_play = 0; g->opts = opt; + highscore_load(g); + /* Initial 3 random blocks */ gamestate_new_block(g); gamestate_new_block(g); @@ -279,6 +282,7 @@ int gamestate_tick(struct gfx_state *s, struct gamestate *g, int d, void (*callb /* Free all data associated with the gamestate */ void gamestate_clear(struct gamestate *g) { + highscore_save(g); gameoptions_destroy(g->opts); free(g->grid_data_ptr); /* Free grid data */ free(g->grid); /* Free pointers to data slots */ diff --git a/src/highscore.c b/src/highscore.c new file mode 100644 index 0000000..3ac9350 --- /dev/null +++ b/src/highscore.c @@ -0,0 +1,75 @@ +#include +#include +#include +#include +#include +#include "engine.h" + +const char *hs_dir_name = "2048"; +const char *hs_file_name = "highscore"; + +static const char* highscore_retrieve_file(void) +{ + static char buffer[4096]; + + if (getenv("XDG_DATA_HOME") != NULL) { + snprintf(buffer, sizeof(buffer), "%s/%s/%s", + getenv("XDG_DATA_HOME"), hs_dir_name, hs_file_name); + } + else if (getenv("HOME")) { + snprintf(buffer, sizeof(buffer), "%s/.local/share/%s/%s", + getenv("HOME"), hs_dir_name, hs_file_name); + } + else { + return hs_file_name; + } + + /* Create file only if it doesn't exist */ + if (access(buffer, F_OK) != -1) + return buffer; + + char *sep = strrchr(buffer, '/'); + while (sep != NULL) { + *sep = '\0'; + if (strlen(buffer) != 0) + mkdir(buffer, 0777); + char *tmpsep = sep; + sep = strrchr(buffer, '/'); + *tmpsep = '/'; + } + + return buffer; +} + +void highscore_reset(void) +{ + const char *hsfile = highscore_retrieve_file(); + + FILE *fd = fopen(hsfile, "w+"); + fprintf(fd, "%d", 0); + fclose(fd); +} + +void highscore_load(struct gamestate *g) +{ + const char *hsfile = highscore_retrieve_file(); + + FILE *fd = fopen(hsfile, "r"); + if (fd == NULL) + fd = fopen(hsfile, "w+"); + + fscanf(fd, "%ld", &g->score_high); + fclose(fd); +} + +void highscore_save(struct gamestate *g) +{ + if (g->score < g->score_high || g->opts->grid_width != 4 || g->opts->grid_height != 4) + return; + + const char *hsfile = highscore_retrieve_file(); + + FILE *fd = fopen(hsfile, "w"); + fprintf(fd, "%ld", g->score); + fclose(fd); +} diff --git a/src/highscore.h b/src/highscore.h new file mode 100644 index 0000000..fe2c542 --- /dev/null +++ b/src/highscore.h @@ -0,0 +1,8 @@ +#ifndef HIGHSCORE_H +#define HIGHSCORE_H + +void highscore_reset(void); +void highscore_load(struct gamestate *g); +void highscore_save(struct gamestate *g); + +#endif diff --git a/src/options.c b/src/options.c index a734e01..2fa45a6 100644 --- a/src/options.c +++ b/src/options.c @@ -1,5 +1,6 @@ #include #include +#include "highscore.h" #include "options.h" void print_usage(void) @@ -77,6 +78,7 @@ struct gameoptions* parse_options(struct gameoptions *opt, int argc, char **argv opt->spawn_rate = strtol(optarg, NULL, 10); break; case 'r': + highscore_reset(); exit(0); case 'h': print_usage(); From d8cef1def4a248b7fbddfa9362d43efdf3ebc71b Mon Sep 17 00:00:00 2001 From: Tiehuis Date: Sun, 22 Feb 2015 16:08:11 +1300 Subject: [PATCH 17/19] Rewritten man page. Removed large dependencies by writing directly for man. --- Makefile | 3 +-- man/2048.1 | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/options.c | 17 +-------------- 3 files changed, 60 insertions(+), 18 deletions(-) create mode 100644 man/2048.1 diff --git a/Makefile b/Makefile index 0807672..164cbef 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,6 @@ obj/%.o: src/%.c remake: clean all clean: - rm -f obj/* - rm -f 2048 + rm -f $(O_FILES) $(PROGRAM) .PHONY: clean remake diff --git a/man/2048.1 b/man/2048.1 new file mode 100644 index 0000000..db81d97 --- /dev/null +++ b/man/2048.1 @@ -0,0 +1,58 @@ +.TH 2048 1 + +.SH NAME +2048 \- play the game 2048 in your terminal + +.SH SYNOPSIS +.B 2048 +[\fB\-hrcCaA\fR] +[\fB\-s\fR \fISIZE\fR] +[\fB\-b\fR \fIRATE\fR] +[\fB\-g\fR \fIGOAL\fR] + +.SH DESCRIPTION +.B 2048 +is an implementation of the popular game, 2048, designed to be run on a +terminal. It is deisgned to be easy to understand and extend. + +.SH CONTROLS +.TP +.BR hjkl " " and " " wasd +Default movement keys. Depending on the graphics implementation, there may be +extra alternatives. +.TP +.BR \q +Quit the current game. + +.SH OPTIONS +.TP +.BR \-h +Print the program usage. +.TP +.BR \-c +Turn on color support (default). +.TP +.BR \-C +Turn off color support. +.TP +.BR \-a +Turn on animations (default). +.TP +.BR \-A +Turn off animations. +.TP +.BR \-s " " \fISIZE\fR +Set the size of the playing field. Default is 4. Maximum value is 16, minimum is 4. +.TP +.BR \-b " " \fIRATE\fR +Set the rate at which blocks are spawned. Default is 1. +.TP +.BR \-g " " \fIGOAL\fR +Set the target end condition. Default is 2048. + +.SH AUTHORS +Originally written by Tiehuis. +All contributions can be found at \fIhttps://github.com/Tiehuis/2048-cli\fR. + +.SH COPYRIGHT +MIT License (2014) diff --git a/src/options.c b/src/options.c index 2fa45a6..52d7d26 100644 --- a/src/options.c +++ b/src/options.c @@ -5,22 +5,7 @@ void print_usage(void) { - printf( - "usage: 2048 [-cCaArh] [-g ] [-b ] [-s ]\n" - "\n" - "controls\n" - " hjkl movement keys\n" - " q quit current game\n" - "\n" - "options\n" - " -s set the grid side lengths\n" - " -b set the block spawn rate\n" - " -g set a new goal (default 2048)\n" - " -a enable animations (default)\n" - " -A disable animations\n" - " -c enable color support\n" - " -C disable color support (default)\n" - ); + printf("usage: 2048 [-cCaArh] [-s SIZE] [-b RATE] [-g GOAL]\n"); } From 5d6c3f92bed1c15c2018594746f9a275c5a8d1b3 Mon Sep 17 00:00:00 2001 From: Tiehuis Date: Sun, 22 Feb 2015 16:30:40 +1300 Subject: [PATCH 18/19] Reintroduced color support for ncurses version --- .gitignore | 3 ++- man/2048.1 | 2 +- src/gfx_curses.c | 27 +++++++++++++++++++++++++++ src/main.c | 1 + 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 6d260d6..fd8eb11 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -/2048* +2048* +*.o *.core diff --git a/man/2048.1 b/man/2048.1 index db81d97..a763b9c 100644 --- a/man/2048.1 +++ b/man/2048.1 @@ -51,7 +51,7 @@ Set the rate at which blocks are spawned. Default is 1. Set the target end condition. Default is 2048. .SH AUTHORS -Originally written by Tiehuis. +Originally written by Marc Tiehuis. All contributions can be found at \fIhttps://github.com/Tiehuis/2048-cli\fR. .SH COPYRIGHT diff --git a/src/gfx_curses.c b/src/gfx_curses.c index ee6fa8a..d9869ef 100644 --- a/src/gfx_curses.c +++ b/src/gfx_curses.c @@ -8,6 +8,8 @@ #define GFX_EXTRA_RIGHT case KEY_RIGHT: #define GFX_EXTRA_LEFT case KEY_LEFT: +#define NUMBER_OF_COLORS 7 + #define iterate(n, expression)\ do {\ int i;\ @@ -32,9 +34,32 @@ struct gfx_state* gfx_init(struct gamestate *g) s->window_width = g->opts->grid_width * (g->print_width + 2) + 1; s->window = newwin(s->window_height, s->window_width, 1, 1); keypad(s->window, TRUE); + + if (g->opts->enable_color && has_colors()) { + start_color(); + + int x = 0; + init_pair(x++, 1, 0); + init_pair(x++, 2, 0); + init_pair(x++, 3, 0); + init_pair(x++, 4, 0); + init_pair(x++, 5, 0); + init_pair(x++, 6, 0); + init_pair(x++, 7, 0); + char dummy[x == NUMBER_OF_COLORS ? 1 : -1]; + } + return s; } +static int int_log2(int n) +{ + int k = 0; + while (n) + ++k, n /= 2; + return k; +} + void gfx_draw(struct gfx_state *s, struct gamestate *g) { if (g->score_last) @@ -54,7 +79,9 @@ void gfx_draw(struct gfx_state *s, struct gamestate *g) for (x = 0; x < g->opts->grid_width; ++x) { if (g->grid[x][y]) { + wattron(s->window, COLOR_PAIR(int_log2(g->grid[x][y]) % NUMBER_OF_COLORS)); mvwprintw(s->window, ypos, xpos, "%*d", g->print_width, g->grid[x][y]); + wattroff(s->window, COLOR_PAIR(int_log2(g->grid[x][y]) % NUMBER_OF_COLORS)); mvwprintw(s->window, ypos, xpos + g->print_width, " |"); } else { diff --git a/src/main.c b/src/main.c index f683559..a724ed2 100644 --- a/src/main.c +++ b/src/main.c @@ -47,6 +47,7 @@ get_new_key:; /* Game will only end if 0 moves available */ if (game_running) { + /* Maybe change this behaviour so if we don't move, we still generate a block */ if (gamestate_tick(s, g, direction, g->opts->animate ? draw_then_sleep : NULL)) gamestate_new_block(g); From 53e941bf983942f014f8326dabd29d01604c193d Mon Sep 17 00:00:00 2001 From: Tiehuis Date: Sun, 22 Feb 2015 16:38:53 +1300 Subject: [PATCH 19/19] Remove the c99 for loops, just for gcc users. Optional CC var set in makefile --- Makefile | 2 +- src/engine.c | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 164cbef..39222a3 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -CC := clang +CC ?= clang CFLAGS += -g -Wall -Wextra LFLAGS += DEFINES := -DVT100 $(shell pkg-config --cflags sdl2) diff --git a/src/engine.c b/src/engine.c index cb10a00..f3bac09 100644 --- a/src/engine.c +++ b/src/engine.c @@ -154,9 +154,10 @@ int gamestate_end_condition(struct gamestate *g) { int ret = -1; //size_t blocks_counted = 0; + int x, y; - for (int x = 0; x < g->opts->grid_width; ++x) { - for (int y = 0; y < g->opts->grid_height; ++y) { + for (x = 0; x < g->opts->grid_width; ++x) { + for (y = 0; y < g->opts->grid_height; ++y) { if (g->grid[x][y] >= g->opts->goal) return 1; if (!g->grid[x][y] || ((x + 1 < g->opts->grid_width) && @@ -238,8 +239,9 @@ struct gamestate* gamestate_init(struct gameoptions *opt) if (!g->grid) goto grid_alloc_fail; /* Switch to two allocation version */ + int i; long **iterator = g->grid; - for (int i = 0; i < g->gridsize; i += opt->grid_width) + for (i = 0; i < g->gridsize; i += opt->grid_width) *iterator++ = &g->grid_data_ptr[i]; g->moved = 0;