diff --git a/.gitignore b/.gitignore index 2c1b2d2..b6fa124 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ snake *.swp +*.o diff --git a/Makefile b/Makefile index cca9cf3..5acd757 100644 --- a/Makefile +++ b/Makefile @@ -6,12 +6,18 @@ LIBS += $(if $(shell pkg-config --exists ncursesw && echo y),\ $(shell pkg-config --libs ncurses),-lcurses)) CFLAGS = -Wall -std=c99 +SRCS = snake.c pipe.c util.c render.c +OBJS = $(SRCS:.c=.o) + all: $(TARGET) -$(TARGET): snake.c - $(CC) -o$@ $(CFLAGS) $< $(LIBS) +$(TARGET): $(OBJS) + $(CC) -o$@ $(CFLAGS) $^ $(LIBS) + +.c.o: + $(CC) $(CFLAGS) -o$@ -c $< clean: - -rm -f $(TARGET) + -rm -f $(TARGET) $(OBJS) .PHONY: all clean diff --git a/pipe.c b/pipe.c new file mode 100644 index 0000000..0cb9249 --- /dev/null +++ b/pipe.c @@ -0,0 +1,92 @@ +#include +#include +#include "pipe.h" +#include "util.h" + +//The states describe the current "velocity" of a pipe. Notice that the allowed +//transitions are given by a change of +/- 1. So, for example, a pipe heading +//left may go up or down but not right. +// Right Down Left Up +const char states[][2] = {{1,0}, {0,1}, {-1,0}, {0,-1}}; + +//The transition matrices here describe the character that should be output upon +//a transition from] one state (direction) to another. If you wanted, you could +//modify these to include the probability of a transition. +//┓ : U+2513 +//┛ : U+251B +//┗ : U+2517 +//┏ : U+250F +const char* trans_unicode[] = { +// R D L U + "", "\u2513", "", "\u251B", //R + "\u2517", "", "\u251B", 0 , //D + "", "\u250F", "", "\u2517", //L + "\u250F", "", "\u2513", 0 //D +}; +const char* trans_ascii[] = { +// R D L U + "", "+", "", "+", //R + "+", "", "+", 0 , //D + "", "+", "", "+", //L + "+", "", "+", 0 //U +}; + +const char * transition_char(const char **list, int row, int col){ + return list[row * 4 + col]; +} + +//The characters to represent a travelling (or extending, I suppose) pipe. +const char* unicode_pipe_chars[] = {"\u2501", "\u2503"}; +const char* ascii_pipe_chars[] = {"-", "|"}; + +void init_pipe(struct pipe *pipe, int ncolours, int initial_state, + int width, int height){ + + if(initial_state < 0) + pipe->state = randrange(0, 4); + else + pipe->state = initial_state; + pipe->colour = randrange(0, ncolours); + pipe->length = 0; + pipe->x = randrange(0, width); + pipe->y = randrange(0, height); +} + +void move_pipe(struct pipe *pipe){ + pipe->x += states[pipe->state][0]; + pipe->y += states[pipe->state][1]; + pipe->length++; +} + +bool wrap_pipe(struct pipe *pipe, int width, int height){ + if(pipe->x < 0 || pipe->x == width + || pipe->y < 0 || pipe->y == height){ + if(pipe->x < 0){ pipe->x += width; } + if(pipe->y < 0){ pipe->y += height; } + if(pipe->x >= width) {pipe->x -= width; } + if(pipe->y >= height) {pipe->y -= height; } + return true; + } + return false; +} + +void random_pipe_colour(struct pipe *pipe, int ncolours){ + pipe->colour = randrange(0, ncolours); +} + +char flip_pipe_state(struct pipe *pipe){ + char old_state = pipe->state; + char new_state = pipe->state; + char flip = randbool(0.5) ? -1 : 1; + new_state += flip; + if(new_state < 0) { new_state = 3; } + else if(new_state > 3){ new_state = 0; } + pipe->state = new_state; + pipe->length = 0; + return old_state; +} + +bool should_flip_state(struct pipe *p, int min_len, float prob){ + return p->length > min_len && randbool(prob); +} + diff --git a/pipe.h b/pipe.h new file mode 100644 index 0000000..3da184a --- /dev/null +++ b/pipe.h @@ -0,0 +1,32 @@ +#ifndef PIPE_H_ +#define PIPE_H_ + +//States and transition characters +extern const char states[][2]; + +//The characters to represent a travelling (or extending, I suppose) pipe. +extern const char* unicode_pipe_chars[]; +extern const char* ascii_pipe_chars[]; + +//Represents a pipe +struct pipe { + unsigned char state; + unsigned short colour; + unsigned short length; + int x, y; +}; + +void init_pipe(struct pipe *pipe, int ncolours, int initial_state, + int width, int height); +void move_pipe(struct pipe *pipe); +bool wrap_pipe(struct pipe *pipe, int width, int height); +char flip_pipe_state(struct pipe *pipe); +void random_pipe_colour(struct pipe *pipe, int ncolours); +bool should_flip_state(struct pipe *p, int min_len, float prob); +char pipe_char(struct pipe *p, char old_state); + +extern const char *trans_ascii[]; +extern const char *trans_unicode[]; +const char * transition_char(const char **list, int row, int col); + +#endif //PIPE_H_ diff --git a/render.c b/render.c new file mode 100644 index 0000000..9b1119e --- /dev/null +++ b/render.c @@ -0,0 +1,59 @@ +#define _POSIX_C_SOURCE 199309L +#include +#include +#include +#include "render.h" +#include "pipe.h" + +#define NS 1000000000L //1ns + +void init_colours(){ + //Initialise colour pairs if we can. + if(has_colors()){ + start_color(); + for(short i=1; i < COLORS; i++){ + init_pair(i, i, COLOR_BLACK); + } + } +} + +void animate(int fps, anim_function renderer, + volatile sig_atomic_t *interrupted, void *data){ + //Store start time + struct timespec start_time; + long delay_ns = NS / fps; + + + while(!(*interrupted)){ + clock_gettime(CLOCK_REALTIME, &start_time); + + //Render + (*renderer)(data); + + //Get end time + struct timespec end_time; + clock_gettime(CLOCK_REALTIME, &end_time); + //Work out how long that took + long took_ns = + NS * (end_time.tv_sec - start_time.tv_sec) + + (end_time.tv_nsec - start_time.tv_nsec); + //Sleep for delay_ns - render time + struct timespec sleep_time = { + .tv_sec = (delay_ns - took_ns) / NS, + .tv_nsec = (delay_ns - took_ns) % NS + }; + nanosleep(&sleep_time, NULL); + } +} + +void render_pipe(struct pipe *p, const char **trans, const char **pipe_chars, + int old_state, int new_state){ + + move(p->y, p->x); + attron(COLOR_PAIR(p->colour)); + if(old_state != new_state) + addstr(transition_char(trans, old_state, new_state)); + else + addstr(pipe_chars[old_state % 2]); + attroff(COLOR_PAIR(p->colour)); +} diff --git a/render.h b/render.h new file mode 100644 index 0000000..7802b93 --- /dev/null +++ b/render.h @@ -0,0 +1,13 @@ +#ifndef RENDER_H_ +#define RENDER_H_ +#include "pipe.h" + +typedef void (*anim_function)(void *data); + +void init_colours(); +void animate(int fps, anim_function renderer, + volatile sig_atomic_t *interrupted, void *data); +void render_pipe(struct pipe *p, const char **trans, const char **pipe_chars, + int old_state, int new_state); + +#endif //RENDER_H_ diff --git a/snake.c b/snake.c index 455f807..9e316b8 100644 --- a/snake.c +++ b/snake.c @@ -9,7 +9,8 @@ #include #include -#define NS 1000000000L //1ns +#include "pipe.h" +#include "render.h" void interrupt_signal(int param); void parse_options(int argc, char **argv); @@ -17,46 +18,7 @@ float parse_float_opt(const char *optname); int parse_int_opt(const char *optname); void die(); void usage_msg(int exitval); - -//The states describe the current "velocity" of a pipe. Notice that the allowed -//transitions are given by a change of +/- 1. So, for example, a pipe heading -//left may go up or down but not right. -// Right Down Left Up -const char states[][2] = {{1,0}, {0,1}, {-1,0}, {0,-1}}; - -//The transition matrices here describe the character that should be output upon -//a transition from] one state (direction) to another. If you wanted, you could -//modify these to include the probability of a transition. -//┓ : U+2513 -//┛ : U+251B -//┗ : U+2517 -//┏ : U+250F -const char* trans_unicode[][4] = { -// R D L U - {"", "\u2513", "", "\u251B" }, //R - {"\u2517", "", "\u251B", 0 }, //D - {"", "\u250F", "", "\u2517" }, //L - {"\u250F", "", "\u2513", 0 } //D -}; -const char* trans_ascii[][4] = { -// R D L U - {"", "+", "", "+"}, //R - {"+", "", "+", 0 }, //D - {"", "+", "", "+"}, //L - {"+", "", "+", 0 } //U -}; - -//The characters to represent a travelling (or extending, I suppose) pipe. -const char* unicode_pipe_chars[] = {"\u2501", "\u2503"}; -const char* ascii_pipe_chars[] = {"-", "|"}; - -//Represents a pipe -struct pipe { - unsigned char state; - unsigned short colour; - unsigned short length; - int x, y; -}; +void render(void *data); //If set >= zero, this initial state is used. int initial_state = -1; @@ -68,7 +30,7 @@ const char *usage = " -f, --fps=F Frames per second. (Default: 60.0 )\n" " -a, --ascii ASCII mode. (Default: no )\n" " -l, --length=N Minimum length of pipe. (Default: 2 )\n" - " -r, --prob=N Probability of chaning direction. (Default: 0.1 )\n" + " -r, --prob=N Probability of changing direction.(Default: 0.1 )\n" " -i, --init=N Initial state (0,1,2,3 => R,D,L,U)(Default: random)\n" " -h, --help This help message.\n"; @@ -96,11 +58,13 @@ unsigned int num_pipes = 20; float fps = 60; float prob = 0.1; unsigned int min_len = 2; -const char* (*trans)[][4] = &trans_unicode; -const char* (*pipe_chars)[] = &unicode_pipe_chars; + +const char **trans = trans_unicode; +const char **pipe_chars = unicode_pipe_chars; int main(int argc, char **argv){ + srand(time(NULL)); setlocale(LC_ALL, ""); //Set a flag upon interrupt to allow proper cleaning signal(SIGINT, interrupt_signal); @@ -111,80 +75,35 @@ int main(int argc, char **argv){ initscr(); curs_set(0); getmaxyx(stdscr, height, width); - //Initialise colour pairs if we can. - if(has_colors()){ - start_color(); - for(short i=1; i < COLORS; i++){ - init_pair(i, i, COLOR_BLACK); - } - } + init_colours(); //Init pipes. Use predetermined initial state, if any. pipes = malloc(num_pipes * sizeof(struct pipe)); - for(unsigned int i=0; i= width) {pipes[i].x -= width; } - if(pipes[i].y >= height) {pipes[i].y -= height; } - pipes[i].colour = (rand() / (RAND_MAX / COLORS + 1)); - } +void render(void *data){ + for(int i=0; i min_len){ - char new_state = pipes[i].state; - char flip = ((rand() < RAND_MAX/2) ? -1 : 1 ); - new_state += flip; - if(new_state < 0) { new_state = 3; } - else if(new_state > 3){ new_state = 0; } - addstr((*trans)[pipes[i].state][(int)new_state]); - pipes[i].length = 0; - pipes[i].state = new_state; - }else{ - addstr((*pipe_chars)[pipes[i].state % 2]); - } - attroff(COLOR_PAIR(pipes[i].colour)); + char old_state = pipes[i].state; + if(should_flip_state(&pipes[i], min_len, prob)){ + old_state = flip_pipe_state(&pipes[i]); } - refresh(); - struct timespec end_time; - clock_gettime(CLOCK_REALTIME, &end_time); - long took_ns = - NS * (end_time.tv_sec - start_time.tv_sec) - + (end_time.tv_nsec - start_time.tv_nsec); - struct timespec sleep_time = { - .tv_sec = (delay_ns - took_ns) / NS, - .tv_nsec = (delay_ns - took_ns) % NS - }; - nanosleep(&sleep_time, NULL); + render_pipe(&pipes[i], trans, pipe_chars, old_state, pipes[i].state); } - - curs_set(1); - endwin(); - free(pipes); - return 0; + refresh(); } void interrupt_signal(int param){ @@ -232,8 +151,8 @@ void parse_options(int argc, char **argv){ fps = parse_float_opt("--fps"); break; case 'a': - trans = &trans_ascii; - pipe_chars = &ascii_pipe_chars; + trans = trans_ascii; + pipe_chars = ascii_pipe_chars; break; case 'l': min_len = parse_int_opt("--length"); diff --git a/util.c b/util.c new file mode 100644 index 0000000..2a91dea --- /dev/null +++ b/util.c @@ -0,0 +1,39 @@ +#include +#include +#include +#include "util.h" + +///Get a random integer in the range [lo, hi). +int randrange(int lo, int hi){ + return randint(lo, hi - 1); +} + +///Get a random integer in the range [lo, hi]. +int randint(int lo, int hi){ + // Note that the distribution will be biased when (hi - lo) is close to + // RAND_MAX. This is unlikely to matter for our purposes. + // See C FAQ 13.16: http://c-faq.com/lib/randrange.html + + assert(lo <= hi /* Range must contain values. */); + unsigned int nbins = (hi - lo) + 1u; + unsigned int nrands = RAND_MAX + 1u; + assert(nbins <= nrands /* Cannot generate > RAND_MAX possible values. */); + + unsigned int r = rand(); + int y = r / (nrands / nbins); + return y + lo; +} + +///Get a random boolean with probability `p` of `true`. +bool randbool(float p){ + assert(0 <= p && p <= 1 /* `p` is a probability. */); + return rand() < (RAND_MAX + 1u) * p; +} + +int min(int a, int b){ + return (a < b) ? a : b; +} + +int max(int a, int b){ + return (a > b) ? a : b; +} diff --git a/util.h b/util.h new file mode 100644 index 0000000..e2cefcf --- /dev/null +++ b/util.h @@ -0,0 +1,10 @@ +#ifndef UTIL_H_ +#define UTIL_H_ + +int randrange(int lo, int hi); +int randint(int lo, int hi); +bool randbool(float p); +int min(int a, int b); +int max(int a, int b); + +#endif //UTIL_H_