Major rewrite. Abstracting graphics code completely and allowing new extensions via a consistent drawing interface
This commit is contained in:
		
							
								
								
									
										29
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								Makefile
									
									
									
									
									
								
							| @@ -1,14 +1,27 @@ | |||||||
| CC	   ?= gcc | CC	    := clang | ||||||
| CFLAGS += -Wall -Wextra | CFLAGS  += -O2 -Wall -Wextra | ||||||
| DEFS	= -DVT100_COMPATIBLE | LFLAGS  += | ||||||
| LIBS	=  | DEFINES := -DVT100 | ||||||
|  |  | ||||||
| all: 2048 | PROGRAM := 2048 | ||||||
|  | C_FILES := $(wildcard src/*.c) | ||||||
|  | O_FILES := $(addprefix obj/,$(notdir $(C_FILES:.c=.o))) | ||||||
|  |  | ||||||
| 2048: src/2048_engine.c src/2048_rewrite.c | all: curses | ||||||
| 	$(CC) $(DEFS) src/2048_engine.c src/2048_rewrite.c -o 2048 $(LIBS) |  | ||||||
|  | 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 | ||||||
|  |  | ||||||
| clean: | clean: | ||||||
|  | 	rm -f obj/* | ||||||
| 	rm -f 2048 | 	rm -f 2048 | ||||||
|  |  | ||||||
| .PHONY: clean | .PHONY: clean remake | ||||||
|   | |||||||
| @@ -1,62 +0,0 @@ | |||||||
| #ifndef _2048_ENGINE |  | ||||||
| #define _2048_ENGINE |  | ||||||
|  |  | ||||||
| #include <stdio.h> |  | ||||||
|  |  | ||||||
| #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 |  | ||||||
| @@ -1,191 +0,0 @@ | |||||||
| #include <stdio.h> |  | ||||||
| #include <stdlib.h> |  | ||||||
| #include <string.h> |  | ||||||
| #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 <windows.h> |  | ||||||
| #define syssleep(s) Sleep(s) |  | ||||||
| #elif defined __unix__ |  | ||||||
| #include <unistd.h> |  | ||||||
| #define syssleep(s) usleep((s) * 1000) |  | ||||||
| #else  |  | ||||||
| #error "Unsupported os: no sleep function" |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| #ifdef HAVE_CURSES |  | ||||||
| #include <ncurses.h> |  | ||||||
| #else |  | ||||||
| #include <termios.h> |  | ||||||
| #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; |  | ||||||
| } |  | ||||||
| @@ -1,11 +1,14 @@ | |||||||
| #include <stdlib.h> | #include <stdlib.h> | ||||||
| #include <time.h> | #include <time.h> | ||||||
| #include "2048_engine.h" | #include "engine.h" | ||||||
| 
 | 
 | ||||||
| /* Utilize block counter to improve some of the functions so they can run
 | /* Utilize block counter to improve some of the functions so they can run
 | ||||||
|  * quicker */ |  * 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)\ | #define swap_if_space(xoff, yoff)\ | ||||||
| @@ -18,7 +21,7 @@ void gravitate(struct gamestate *g, direction d, void (*callback)(struct gamesta | |||||||
|         }\ |         }\ | ||||||
|     } while (0) |     } while (0) | ||||||
| 
 | 
 | ||||||
|     size_t x, y; |     int x, y; | ||||||
|     int done = 0; |     int done = 0; | ||||||
| 
 | 
 | ||||||
|     if (d == dir_left) { |     if (d == dir_left) { | ||||||
| @@ -30,7 +33,7 @@ void gravitate(struct gamestate *g, direction d, void (*callback)(struct gamesta | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             if (callback) |             if (callback) | ||||||
|                 callback(g); |                 callback(s, g); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     else if (d == dir_right) { |     else if (d == dir_right) { | ||||||
| @@ -42,7 +45,7 @@ void gravitate(struct gamestate *g, direction d, void (*callback)(struct gamesta | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             if (callback) |             if (callback) | ||||||
|                 callback(g); |                 callback(s, g); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     else if (d == dir_down) { |     else if (d == dir_down) { | ||||||
| @@ -54,7 +57,7 @@ void gravitate(struct gamestate *g, direction d, void (*callback)(struct gamesta | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             if (callback) |             if (callback) | ||||||
|                 callback(g); |                 callback(s, g); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     else if (d == dir_up) { |     else if (d == dir_up) { | ||||||
| @@ -66,7 +69,7 @@ void gravitate(struct gamestate *g, direction d, void (*callback)(struct gamesta | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             if (callback) |             if (callback) | ||||||
|                 callback(g); |                 callback(s, g); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     else { |     else { | ||||||
| @@ -77,7 +80,16 @@ void gravitate(struct gamestate *g, direction d, void (*callback)(struct gamesta | |||||||
| #undef swap_if_space | #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)\ | #define merge_if_equal(xoff, yoff)\ | ||||||
| @@ -92,7 +104,7 @@ void merge(struct gamestate *g, direction d, void (*callback)(struct gamestate * | |||||||
|         }\ |         }\ | ||||||
|     } while (0) |     } while (0) | ||||||
| 
 | 
 | ||||||
|     size_t x, y; |     int x, y; | ||||||
|     g->score_last = 0; |     g->score_last = 0; | ||||||
| 
 | 
 | ||||||
|     if (d == dir_left) { |     if (d == dir_left) { | ||||||
| @@ -129,28 +141,21 @@ void merge(struct gamestate *g, direction d, void (*callback)(struct gamestate * | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (callback) |     if (callback) | ||||||
|         callback(g); |         callback(s, g); | ||||||
| 
 | 
 | ||||||
| #undef merge_if_equal | #undef merge_if_equal | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* Return -1 on lose condition, 1 on win condition, 0 on
 | /* Scan the current board and determine if an end outcome has been reached.
 | ||||||
|  * haven't ended */ |  * -1 indicates a lose condition, 1 indicates a win condition, 0 indicates | ||||||
| int end_condition(struct gamestate *g) |  *  end has not yet been reached. */ | ||||||
|  | int gamestate_end_condition(struct gamestate *g) | ||||||
| { | { | ||||||
|     int ret = -1; |     int ret = -1; | ||||||
|     size_t blocks_counted = 0; |     //size_t blocks_counted = 0;
 | ||||||
| 
 | 
 | ||||||
|     size_t x, y; |     for (int x = 0; x < g->opts->grid_width; ++x) { | ||||||
|     for (x = 0; x < g->opts->grid_width; ++x) { |         for (int y = 0; y < g->opts->grid_height; ++y) { | ||||||
|         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) |             if (g->grid[x][y] >= g->opts->goal) | ||||||
|                 return 1; |                 return 1; | ||||||
|             if (!g->grid[x][y] || ((x + 1 < g->opts->grid_width) && |             if (!g->grid[x][y] || ((x + 1 < g->opts->grid_width) && | ||||||
| @@ -164,10 +169,8 @@ int end_condition(struct gamestate *g) | |||||||
|     return ret; |     return ret; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* Find a better method for getting a random square. It would be useful to keep
 | /* Place a random block into the current grid */ | ||||||
|  * track of how many blocks are in play, which we can query here, end_condition | void gamestate_new_block(struct gamestate *g) | ||||||
|  * to improve them. */ |  | ||||||
| void random_block(struct gamestate *g) |  | ||||||
| { | { | ||||||
|     /* pick random square, if it is full, then move forward until we find
 |     /* pick random square, if it is full, then move forward until we find
 | ||||||
|      * an empty square. This is biased */ |      * an empty square. This is biased */ | ||||||
| @@ -186,7 +189,7 @@ void random_block(struct gamestate *g) | |||||||
|      */ |      */ | ||||||
| 
 | 
 | ||||||
|     /* Error here */ |     /* Error here */ | ||||||
| #ifdef NULLO | #ifdef SKIP | ||||||
|     size_t block_position = (size_t)rand() % ( |     size_t block_position = (size_t)rand() % ( | ||||||
|             g->opts->grid_width * g->opts->grid_height - g->blocks_in_play); |             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
 | /* This returns the number of digits in the base10 rep of n. The ceiling is
 | ||||||
|  * taken so this will be one greater than required */ |  * 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; |     int l = 0; | ||||||
|     while (n) n /= 10, ++l; |     while (n) n /= 10, ++l; | ||||||
|     return l + 1; |     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) | struct gamestate* gamestate_init(struct gameoptions *opt) | ||||||
| { | { | ||||||
|     if (!opt) return NULL; |     if (!opt) return NULL; | ||||||
| @@ -223,146 +230,58 @@ struct gamestate* gamestate_init(struct gameoptions *opt) | |||||||
|     if (!g) goto gamestate_alloc_fail; |     if (!g) goto gamestate_alloc_fail; | ||||||
|     g->gridsize = opt->grid_width * opt->grid_height; |     g->gridsize = opt->grid_width * opt->grid_height; | ||||||
| 
 | 
 | ||||||
|     long *grid_back = calloc(g->gridsize, sizeof(long)); |     g->grid_data_ptr = calloc(g->gridsize, sizeof(long)); | ||||||
|     if (!grid_back) goto grid_back_alloc_fail; |     if (!g->grid_data_ptr) goto grid_data_alloc_fail; | ||||||
| 
 | 
 | ||||||
|     g->grid = malloc(opt->grid_height * sizeof(long*)); |     g->grid = malloc(opt->grid_height * sizeof(long*)); | ||||||
|     if (!g->grid) goto grid_alloc_fail; |     if (!g->grid) goto grid_alloc_fail; | ||||||
| 
 | 
 | ||||||
|     /* Switch to two allocation version */ |     /* Switch to two allocation version */ | ||||||
|     size_t i; |  | ||||||
|     long **iterator = g->grid; |     long **iterator = g->grid; | ||||||
|     for (i = 0; i < g->gridsize; i += opt->grid_width) |     for (int i = 0; i < g->gridsize; i += opt->grid_width) | ||||||
|         *iterator++ = &grid_back[i]; |         *iterator++ = &g->grid_data_ptr[i]; | ||||||
| 
 | 
 | ||||||
|     g->moved = 0; |     g->moved = 0; | ||||||
|     g->score = 0; |     g->score = 0; | ||||||
|     g->score_high = 0; |     g->score_high = 0; | ||||||
|     g->score_last = 0; |     g->score_last = 0; | ||||||
|     g->print_width = clog10(opt->goal); |     g->print_width = digits_ceiling(opt->goal); | ||||||
|     g->blocks_in_play = 0; |     g->blocks_in_play = 0; | ||||||
|     g->opts = opt; |     g->opts = opt; | ||||||
| 
 | 
 | ||||||
|     /* Initial 3 random blocks */ |     /* Initial 3 random blocks */ | ||||||
|     random_block(g); |     gamestate_new_block(g); | ||||||
|     random_block(g); |     gamestate_new_block(g); | ||||||
|     random_block(g); |     gamestate_new_block(g); | ||||||
|     return g; |     return g; | ||||||
| 
 | 
 | ||||||
| grid_alloc_fail: | grid_alloc_fail: | ||||||
|     free(grid_back); |     free(g->grid_data_ptr); | ||||||
| grid_back_alloc_fail: | grid_data_alloc_fail: | ||||||
|     free(g); |     free(g); | ||||||
| gamestate_alloc_fail: | gamestate_alloc_fail: | ||||||
|     return NULL; |     return NULL; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| struct gameoptions* gameoptions_default(void) | /* 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 | ||||||
|     struct gameoptions *opt = malloc(sizeof(struct gameoptions)); |  * functions modify it, we can determine which action to take. */ | ||||||
|     if (!opt) return NULL; | int gamestate_tick(struct gfx_state *s, struct gamestate *g, int d, void (*callback)(struct gfx_state*, struct gamestate*)) | ||||||
| 
 |  | ||||||
|     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*)) |  | ||||||
| { | { | ||||||
|     /* Reset move. Altered by gravitate and merge if we do move */ |     /* Reset move. Altered by gravitate and merge if we do move */ | ||||||
|     g->moved = 0; |     g->moved = 0; | ||||||
|     gravitate(g, d, callback); |     printf("%d\n", d); | ||||||
|     merge(g, d, callback); |     gravitate(s, g, d, callback); | ||||||
|     gravitate(g, d, callback); |     merge(s, g, d, callback); | ||||||
|  |     gravitate(s, g, d, callback); | ||||||
|     return g->moved; |     return g->moved; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /* Free all data associated with the gamestate */ | ||||||
| void gamestate_clear(struct gamestate *g) | void gamestate_clear(struct gamestate *g) | ||||||
| { | { | ||||||
|     free(g->opts); |     gameoptions_destroy(g->opts); | ||||||
|     free(g->grid[0]);   /* Free grid data */ |     free(g->grid_data_ptr);   /* Free grid data */ | ||||||
|     free(g->grid);      /* Free pointers to data slots */ |     free(g->grid);      /* Free pointers to data slots */ | ||||||
|     free(g); |     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 <goal>] [-b <rate>] [-s <size>]\n" |  | ||||||
|     "\n" |  | ||||||
|     "controls\n" |  | ||||||
|     "   hjkl        movement keys\n" |  | ||||||
|     "   q           quit current game\n" |  | ||||||
|     "\n" |  | ||||||
|     "options\n" |  | ||||||
|     "   -s <size>   set the grid side lengths\n" |  | ||||||
|     "   -b <rate>   set the block spawn rate\n" |  | ||||||
|     "   -g <goal>   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 <getopt.h> |  | ||||||
| 
 |  | ||||||
| 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; |  | ||||||
| } |  | ||||||
							
								
								
									
										43
									
								
								src/engine.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/engine.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | #ifndef ENGINE_H | ||||||
|  | #define ENGINE_H | ||||||
|  |  | ||||||
|  | #include <stdio.h> | ||||||
|  | #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 | ||||||
							
								
								
									
										30
									
								
								src/gfx.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/gfx.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
							
								
								
									
										93
									
								
								src/gfx_curses.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								src/gfx_curses.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,93 @@ | |||||||
|  | #include <stdlib.h> | ||||||
|  | #include <ncurses.h> | ||||||
|  | #include <unistd.h> | ||||||
|  | #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(); | ||||||
|  | } | ||||||
							
								
								
									
										72
									
								
								src/gfx_terminal.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/gfx_terminal.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | |||||||
|  | #include <stdio.h> | ||||||
|  | #include <stdlib.h> | ||||||
|  | #include <termios.h> | ||||||
|  | #include <unistd.h> | ||||||
|  | #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); | ||||||
|  | } | ||||||
							
								
								
									
										64
									
								
								src/main.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/main.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | |||||||
|  | #include <stdio.h> | ||||||
|  | #include <stdbool.h> | ||||||
|  | #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; | ||||||
|  | } | ||||||
							
								
								
									
										88
									
								
								src/options.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/options.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | |||||||
|  | #include <stdio.h> | ||||||
|  | #include <stdlib.h> | ||||||
|  | #include "options.h" | ||||||
|  |  | ||||||
|  | void print_usage(void) | ||||||
|  | { | ||||||
|  |     printf( | ||||||
|  |     "usage: 2048 [-cCaArh] [-g <goal>] [-b <rate>] [-s <size>]\n" | ||||||
|  |     "\n" | ||||||
|  |     "controls\n" | ||||||
|  |     "   hjkl        movement keys\n" | ||||||
|  |     "   q           quit current game\n" | ||||||
|  |     "\n" | ||||||
|  |     "options\n" | ||||||
|  |     "   -s <size>   set the grid side lengths\n" | ||||||
|  |     "   -b <rate>   set the block spawn rate\n" | ||||||
|  |     "   -g <goal>   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; | ||||||
|  | } | ||||||
							
								
								
									
										31
									
								
								src/options.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/options.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | #ifndef OPTIONS_H | ||||||
|  | #define OPTIONS_H | ||||||
|  |  | ||||||
|  | #include <getopt.h> | ||||||
|  |  | ||||||
|  | #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 | ||||||
		Reference in New Issue
	
	Block a user