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