Major rewrite. Abstracting graphics code completely and allowing new extensions via a consistent drawing interface
This commit is contained in:
parent
da0ff59971
commit
8cff1bb002
31
Makefile
31
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)))
|
||||||
|
|
||||||
|
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:
|
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,9 +104,9 @@ 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) {
|
||||||
for (x = 0; x < g->opts->grid_width - 1; ++x) {
|
for (x = 0; x < g->opts->grid_width - 1; ++x) {
|
||||||
for (y = 0; y < g->opts->grid_height; ++y) {
|
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)
|
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) &&
|
||||||
(g->grid[x][y] == g->grid[x+1][y]))
|
(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])))
|
(g->grid[x][y] == g->grid[x][y+1])))
|
||||||
ret = 0;
|
ret = 0;
|
||||||
}
|
}
|
||||||
|
@ -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 */
|
||||||
|
@ -179,14 +182,14 @@ void random_block(struct gamestate *g)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fix up this random number generator */
|
/* Fix up this random number generator */
|
||||||
/* Method:
|
/* Method:
|
||||||
* - Find a non-biased index between 0 and blocks_play, n
|
* - Find a non-biased index between 0 and blocks_play, n
|
||||||
* - Find the nth 0 element in the array
|
* - Find the nth 0 element in the array
|
||||||
* - insert a random value there
|
* - insert a random value there
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* 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
|
Loading…
Reference in New Issue
Block a user