From 08114d2adecb5457a272ca4d3e9f9a098cab3259 Mon Sep 17 00:00:00 2001 From: Stefans Mezulis Date: Tue, 21 Apr 2015 21:11:54 +0100 Subject: [PATCH 1/9] Refactor main loop into separate functions Separate the logic for initialising, moving, wrapping and flipping the state of a pipe. --- .gitignore | 1 + Makefile | 12 ++++++-- pipe.c | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++ pipe.h | 28 +++++++++++++++++++ snake.c | 82 ++++++++---------------------------------------------- 5 files changed, 130 insertions(+), 73 deletions(-) create mode 100644 pipe.c create mode 100644 pipe.h 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..687ec64 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 +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..721e8b8 --- /dev/null +++ b/pipe.c @@ -0,0 +1,80 @@ +#include +#include +#include "pipe.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[][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[] = {"-", "|"}; + +void init_pipe(struct pipe *pipe, int ncolours, int initial_state, + int width, int height){ + + if(initial_state < 0) + pipe->state = (rand() / (RAND_MAX / 4 + 1)); + else + pipe->state = initial_state; + pipe->colour = (rand() / (RAND_MAX / ncolours + 1)); + pipe->length = 0; + pipe->x = (rand() / (RAND_MAX / width + 1)); + pipe->y = (rand() / (RAND_MAX / height + 1)); +} + +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 = (rand() / (RAND_MAX / ncolours + 1)); +} + +void flip_pipe_state(struct pipe *pipe){ + char new_state = pipe->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; } + pipe->state = new_state; + pipe->length = 0; +} diff --git a/pipe.h b/pipe.h new file mode 100644 index 0000000..ceadca7 --- /dev/null +++ b/pipe.h @@ -0,0 +1,28 @@ +#ifndef PIPE_H_ +#define PIPE_H_ + +//States and transition characters +extern const char states[][2]; +extern const char* trans_unicode[][4]; +extern const char* trans_ascii[][4]; + +//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); +void flip_pipe_state(struct pipe *pipe); +void random_pipe_colour(struct pipe *pipe, int ncolours); + +#endif //PIPE_H_ diff --git a/snake.c b/snake.c index 455f807..9e23f45 100644 --- a/snake.c +++ b/snake.c @@ -9,6 +9,8 @@ #include #include +#include "pipe.h" + #define NS 1000000000L //1ns void interrupt_signal(int param); @@ -18,46 +20,6 @@ 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; -}; - //If set >= zero, this initial state is used. int initial_state = -1; @@ -121,16 +83,8 @@ int main(int argc, char **argv){ //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)); - } + move_pipe(&pipes[i]); + if(wrap_pipe(&pipes[i], width, height)) + random_pipe_colour(&pipes[i], COLORS); move(pipes[i].y, pipes[i].x); attron(COLOR_PAIR(pipes[i].colour)); if( rand() < prob*RAND_MAX && pipes[i].length > 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; } + char old_state = pipes[i].state; + flip_pipe_state(&pipes[i]); - addstr((*trans)[pipes[i].state][(int)new_state]); - pipes[i].length = 0; - pipes[i].state = new_state; + //Write transition character + addstr((*trans)[(int)old_state][pipes[i].state]); }else{ + //Write continuation character addstr((*pipe_chars)[pipes[i].state % 2]); } attroff(COLOR_PAIR(pipes[i].colour)); From a483ab575e89b62e5895ecd090839f1bd34e929b Mon Sep 17 00:00:00 2001 From: Stefans Mezulis Date: Sat, 23 May 2015 13:34:39 +0100 Subject: [PATCH 2/9] Refactor calls to rand() for integers I know I'll forget the "+1" in `(rand() / (RAND_MAX / width + 1)` at some point, so refactor this into a function called `random_i` (`random` will conflict with a built-in function with `--std=gnu99`). --- Makefile | 2 +- pipe.c | 11 ++++++----- util.c | 15 +++++++++++++++ util.h | 8 ++++++++ 4 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 util.c create mode 100644 util.h diff --git a/Makefile b/Makefile index 687ec64..e96b749 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ LIBS += $(if $(shell pkg-config --exists ncursesw && echo y),\ $(shell pkg-config --libs ncurses),-lcurses)) CFLAGS = -Wall -std=c99 -SRCS = snake.c pipe.c +SRCS = snake.c pipe.c util.c OBJS = $(SRCS:.c=.o) all: $(TARGET) diff --git a/pipe.c b/pipe.c index 721e8b8..aa033f6 100644 --- a/pipe.c +++ b/pipe.c @@ -1,6 +1,7 @@ #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 @@ -38,13 +39,13 @@ void init_pipe(struct pipe *pipe, int ncolours, int initial_state, int width, int height){ if(initial_state < 0) - pipe->state = (rand() / (RAND_MAX / 4 + 1)); + pipe->state = random_i(0, 4); else pipe->state = initial_state; - pipe->colour = (rand() / (RAND_MAX / ncolours + 1)); + pipe->colour = random_i(0, ncolours); pipe->length = 0; - pipe->x = (rand() / (RAND_MAX / width + 1)); - pipe->y = (rand() / (RAND_MAX / height + 1)); + pipe->x = random_i(0, width); + pipe->y = random_i(0, height); } void move_pipe(struct pipe *pipe){ @@ -66,7 +67,7 @@ bool wrap_pipe(struct pipe *pipe, int width, int height){ } void random_pipe_colour(struct pipe *pipe, int ncolours){ - pipe->colour = (rand() / (RAND_MAX / ncolours + 1)); + pipe->colour = random_i(0, ncolours); } void flip_pipe_state(struct pipe *pipe){ diff --git a/util.c b/util.c new file mode 100644 index 0000000..f71bec0 --- /dev/null +++ b/util.c @@ -0,0 +1,15 @@ +#include +#include "util.h" + +///Get a random integer in the range [lo, hi). +int random_i(int lo, int hi){ + return (rand() / (RAND_MAX / (hi - lo) + 1)) + lo; +} + +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..5c6a18f --- /dev/null +++ b/util.h @@ -0,0 +1,8 @@ +#ifndef UTIL_H_ +#define UTIL_H_ + +int random_i(int lo, int hi); +int min(int a, int b); +int max(int a, int b); + +#endif //UTIL_H_ From e3920327b31c36b9fe1fc8640bd564ab0b137f46 Mon Sep 17 00:00:00 2001 From: Stefans Mezulis Date: Sat, 23 May 2015 13:53:25 +0100 Subject: [PATCH 3/9] Refactor to separate animation and rendering Rendering the pipes to the screen and animating a frame are two separate concepts. Rendering takes care of drawing the pipes and flushing everything to the terminal. Animating involves rendering a frame, determining the delay required before the next frame needs rendering and sleeping for that length of time. This commit separtes the two concerns out. The `animate` function now calls an arbitrary renderer, which is passed as a function pointer. The `animate` function could call the rendering function directly, but in the future we may want to pass a list of renders to allow for overlays. --- Makefile | 2 +- render.c | 46 +++++++++++++++++++++++++++++++++++ render.h | 10 ++++++++ snake.c | 73 ++++++++++++++++++++++---------------------------------- 4 files changed, 85 insertions(+), 46 deletions(-) create mode 100644 render.c create mode 100644 render.h diff --git a/Makefile b/Makefile index e96b749..5acd757 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ 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 +SRCS = snake.c pipe.c util.c render.c OBJS = $(SRCS:.c=.o) all: $(TARGET) diff --git a/render.c b/render.c new file mode 100644 index 0000000..45b3c6d --- /dev/null +++ b/render.c @@ -0,0 +1,46 @@ +#define _POSIX_C_SOURCE 199309L +#include +#include +#include +#include "render.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); + } +} diff --git a/render.h b/render.h new file mode 100644 index 0000000..a870b0f --- /dev/null +++ b/render.h @@ -0,0 +1,10 @@ +#ifndef RENDER_H_ +#define RENDER_H_ + +typedef void (*anim_function)(void *data); + +void init_colours(); +void animate(int fps, anim_function renderer, + volatile sig_atomic_t *interrupted, void *data); + +#endif //RENDER_H_ diff --git a/snake.c b/snake.c index 9e23f45..4cc98ae 100644 --- a/snake.c +++ b/snake.c @@ -10,8 +10,7 @@ #include #include "pipe.h" - -#define NS 1000000000L //1ns +#include "render.h" void interrupt_signal(int param); void parse_options(int argc, char **argv); @@ -19,6 +18,7 @@ float parse_float_opt(const char *optname); int parse_int_opt(const char *optname); void die(); void usage_msg(int exitval); +void render(void *data); //If set >= zero, this initial state is used. int initial_state = -1; @@ -73,55 +73,14 @@ 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 min_len){ - char old_state = pipes[i].state; - flip_pipe_state(&pipes[i]); - - //Write transition character - addstr((*trans)[(int)old_state][pipes[i].state]); - }else{ - //Write continuation character - addstr((*pipe_chars)[pipes[i].state % 2]); - } - attroff(COLOR_PAIR(pipes[i].colour)); - } - 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); - } + animate(fps, render, &interrupted, NULL); curs_set(1); endwin(); @@ -129,6 +88,30 @@ int main(int argc, char **argv){ return 0; } +void render(void *data){ + for(int i=0; i min_len){ + char old_state = pipes[i].state; + flip_pipe_state(&pipes[i]); + + //Write transition character + addstr((*trans)[(int)old_state][pipes[i].state]); + }else{ + //Write continuation character + addstr((*pipe_chars)[pipes[i].state % 2]); + } + attroff(COLOR_PAIR(pipes[i].colour)); + } + refresh(); +} + void interrupt_signal(int param){ interrupted = 1; } From 3dd177c20b4bec8763d2c5262a93df678ff1e52a Mon Sep 17 00:00:00 2001 From: Stefans Mezulis Date: Sat, 23 May 2015 13:55:11 +0100 Subject: [PATCH 4/9] Move all pipe-flipping logic into pipe.c This moves the "should flip" condition into pipe.c. It also changes "flip_pipe_state" so that it returns the previous state. --- pipe.c | 8 +++++++- pipe.h | 3 ++- snake.c | 5 ++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/pipe.c b/pipe.c index aa033f6..5167e1d 100644 --- a/pipe.c +++ b/pipe.c @@ -70,7 +70,8 @@ void random_pipe_colour(struct pipe *pipe, int ncolours){ pipe->colour = random_i(0, ncolours); } -void flip_pipe_state(struct pipe *pipe){ +char flip_pipe_state(struct pipe *pipe){ + char old_state = pipe->state; char new_state = pipe->state; char flip = ((rand() < RAND_MAX/2) ? -1 : 1 ); new_state += flip; @@ -78,4 +79,9 @@ void flip_pipe_state(struct pipe *pipe){ 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 rand() < prob*RAND_MAX && p->length > min_len; } diff --git a/pipe.h b/pipe.h index ceadca7..9955cd9 100644 --- a/pipe.h +++ b/pipe.h @@ -22,7 +22,8 @@ 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); -void flip_pipe_state(struct pipe *pipe); +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); #endif //PIPE_H_ diff --git a/snake.c b/snake.c index 4cc98ae..baeaafe 100644 --- a/snake.c +++ b/snake.c @@ -97,9 +97,8 @@ void render(void *data){ move(pipes[i].y, pipes[i].x); attron(COLOR_PAIR(pipes[i].colour)); - if( rand() < prob*RAND_MAX && pipes[i].length > min_len){ - char old_state = pipes[i].state; - flip_pipe_state(&pipes[i]); + if(should_flip_state(&pipes[i], min_len, prob)){ + char old_state = flip_pipe_state(&pipes[i]); //Write transition character addstr((*trans)[(int)old_state][pipes[i].state]); From c89688c885801bdc4a206e3fc6b02d3ddab9ae33 Mon Sep 17 00:00:00 2001 From: Stefans Mezulis Date: Sat, 23 May 2015 14:38:08 +0100 Subject: [PATCH 5/9] Remove completely unnecessary complicated arrays Actually referencing an element in these arrays was a painful exercise. We can get around this by just using C-style arrays of C-strings. --- pipe.c | 25 +++++++++++++++---------- pipe.h | 7 +++++-- snake.c | 14 +++++++------- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/pipe.c b/pipe.c index 5167e1d..4fe9212 100644 --- a/pipe.c +++ b/pipe.c @@ -16,21 +16,25 @@ const char states[][2] = {{1,0}, {0,1}, {-1,0}, {0,-1}}; //┛ : U+251B //┗ : U+2517 //┏ : U+250F -const char* trans_unicode[][4] = { +const char* trans_unicode[] = { // R D L U - {"", "\u2513", "", "\u251B" }, //R - {"\u2517", "", "\u251B", 0 }, //D - {"", "\u250F", "", "\u2517" }, //L - {"\u250F", "", "\u2513", 0 } //D + "", "\u2513", "", "\u251B", //R + "\u2517", "", "\u251B", 0 , //D + "", "\u250F", "", "\u2517", //L + "\u250F", "", "\u2513", 0 //D }; -const char* trans_ascii[][4] = { +const char* trans_ascii[] = { // R D L U - {"", "+", "", "+"}, //R - {"+", "", "+", 0 }, //D - {"", "+", "", "+"}, //L - {"+", "", "+", 0 } //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[] = {"-", "|"}; @@ -85,3 +89,4 @@ char flip_pipe_state(struct pipe *pipe){ bool should_flip_state(struct pipe *p, int min_len, float prob){ return rand() < prob*RAND_MAX && p->length > min_len; } + diff --git a/pipe.h b/pipe.h index 9955cd9..3da184a 100644 --- a/pipe.h +++ b/pipe.h @@ -3,8 +3,6 @@ //States and transition characters extern const char states[][2]; -extern const char* trans_unicode[][4]; -extern const char* trans_ascii[][4]; //The characters to represent a travelling (or extending, I suppose) pipe. extern const char* unicode_pipe_chars[]; @@ -25,5 +23,10 @@ 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/snake.c b/snake.c index baeaafe..cb42bd5 100644 --- a/snake.c +++ b/snake.c @@ -58,8 +58,9 @@ 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){ @@ -99,12 +100,11 @@ void render(void *data){ attron(COLOR_PAIR(pipes[i].colour)); if(should_flip_state(&pipes[i], min_len, prob)){ char old_state = flip_pipe_state(&pipes[i]); - //Write transition character - addstr((*trans)[(int)old_state][pipes[i].state]); + addstr(transition_char(trans, old_state, pipes[i].state)); }else{ //Write continuation character - addstr((*pipe_chars)[pipes[i].state % 2]); + addstr(pipe_chars[pipes[i].state % 2]); } attroff(COLOR_PAIR(pipes[i].colour)); } @@ -156,8 +156,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"); From 9c339ceedd395c88f8102d1635323e17c2b754d6 Mon Sep 17 00:00:00 2001 From: Stefans Mezulis Date: Sat, 23 May 2015 15:21:21 +0100 Subject: [PATCH 6/9] Refactor the rendering of a single pipe Just so that if we want to render a pipe in a separate rendering function (not that any exist yet), it's a bit easier. --- render.c | 13 +++++++++++++ render.h | 3 +++ snake.c | 12 +++--------- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/render.c b/render.c index 45b3c6d..9b1119e 100644 --- a/render.c +++ b/render.c @@ -3,6 +3,7 @@ #include #include #include "render.h" +#include "pipe.h" #define NS 1000000000L //1ns @@ -44,3 +45,15 @@ void animate(int fps, anim_function renderer, 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 index a870b0f..7802b93 100644 --- a/render.h +++ b/render.h @@ -1,10 +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 cb42bd5..2c93699 100644 --- a/snake.c +++ b/snake.c @@ -96,17 +96,11 @@ void render(void *data){ random_pipe_colour(&pipes[i], COLORS); - move(pipes[i].y, pipes[i].x); - attron(COLOR_PAIR(pipes[i].colour)); + char old_state = pipes[i].state; if(should_flip_state(&pipes[i], min_len, prob)){ - char old_state = flip_pipe_state(&pipes[i]); - //Write transition character - addstr(transition_char(trans, old_state, pipes[i].state)); - }else{ - //Write continuation character - addstr(pipe_chars[pipes[i].state % 2]); + old_state = flip_pipe_state(&pipes[i]); } - attroff(COLOR_PAIR(pipes[i].colour)); + render_pipe(&pipes[i], trans, pipe_chars, old_state, pipes[i].state); } refresh(); } From 7d2e17e367c85fff7e44251b373bb9819755a3d0 Mon Sep 17 00:00:00 2001 From: Stefans Mezulis Date: Sun, 18 Feb 2018 12:07:59 +0000 Subject: [PATCH 7/9] Fix random methods and make usage more consistent This commit replaces the function `random_i` with `randrange`. This matches the Python nomenclature of using `randrange` for `[lo, hi)` and `randint` for `[lo, hi]`, and removes a glaringly obvious possibility for a divide-by-zero error when `random_i` was called with `lo == hi`. For the sake of consistency, I also added `randbool` to avoid scattered calls to `rand()` and usage of `RAND_MAX`. Note: The current implementation of `randint` is slightly biased when `hi - lo` is close to `RAND_MAX`, but that really doesn't matter for our application. --- pipe.c | 14 +++++++------- util.c | 28 ++++++++++++++++++++++++++-- util.h | 4 +++- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/pipe.c b/pipe.c index 4fe9212..0cb9249 100644 --- a/pipe.c +++ b/pipe.c @@ -43,13 +43,13 @@ void init_pipe(struct pipe *pipe, int ncolours, int initial_state, int width, int height){ if(initial_state < 0) - pipe->state = random_i(0, 4); + pipe->state = randrange(0, 4); else pipe->state = initial_state; - pipe->colour = random_i(0, ncolours); + pipe->colour = randrange(0, ncolours); pipe->length = 0; - pipe->x = random_i(0, width); - pipe->y = random_i(0, height); + pipe->x = randrange(0, width); + pipe->y = randrange(0, height); } void move_pipe(struct pipe *pipe){ @@ -71,13 +71,13 @@ bool wrap_pipe(struct pipe *pipe, int width, int height){ } void random_pipe_colour(struct pipe *pipe, int ncolours){ - pipe->colour = random_i(0, 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 = ((rand() < RAND_MAX/2) ? -1 : 1 ); + 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; } @@ -87,6 +87,6 @@ char flip_pipe_state(struct pipe *pipe){ } bool should_flip_state(struct pipe *p, int min_len, float prob){ - return rand() < prob*RAND_MAX && p->length > min_len; + return p->length > min_len && randbool(prob); } diff --git a/util.c b/util.c index f71bec0..2a91dea 100644 --- a/util.c +++ b/util.c @@ -1,9 +1,33 @@ +#include #include +#include #include "util.h" ///Get a random integer in the range [lo, hi). -int random_i(int lo, int hi){ - return (rand() / (RAND_MAX / (hi - lo) + 1)) + lo; +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){ diff --git a/util.h b/util.h index 5c6a18f..e2cefcf 100644 --- a/util.h +++ b/util.h @@ -1,7 +1,9 @@ #ifndef UTIL_H_ #define UTIL_H_ -int random_i(int lo, int hi); +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); From 3fb4022427a3ceff223e395bbdd5a43257d354a3 Mon Sep 17 00:00:00 2001 From: Stefans Mezulis Date: Sun, 18 Feb 2018 13:56:53 +0000 Subject: [PATCH 8/9] Fix typo in usage information. --- snake.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snake.c b/snake.c index 2c93699..c0997be 100644 --- a/snake.c +++ b/snake.c @@ -30,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"; From d16165327ce031f739671e4f22f445337bb74374 Mon Sep 17 00:00:00 2001 From: Stefans Mezulis Date: Sun, 18 Feb 2018 16:32:39 +0000 Subject: [PATCH 9/9] Actually seed the PRNG :/ --- snake.c | 1 + 1 file changed, 1 insertion(+) diff --git a/snake.c b/snake.c index c0997be..9e316b8 100644 --- a/snake.c +++ b/snake.c @@ -64,6 +64,7 @@ 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);