/** * @author Steffen Vogel , Toni Uhlig * @copyright Copyright (c) 2010-2014, Steffen Vogel, Toni Uhlig * @license http://opensource.org/licenses/gpl-license.php GNU Public License */ #include #include #include #include #include #include #include /* configuration */ #include "config.h" #if defined(RANDOM_SPAWNS) static uint8_t rnd_spawns = 0xFF; #endif struct config { uint8_t paused:1; uint8_t quit:1; uint8_t menu:1; uint8_t reprint:1; }; struct pattern { uint8_t width; uint8_t height; uint8_t * data; }; struct cursor { uint8_t x; uint8_t y; }; uint8_t start[3][3] = { {0, 1, 1}, {1, 1, 0}, {0, 1, 0} }; uint8_t glider[3][3] = { {0, 1, 0}, {0, 0, 1}, {1, 1, 1} }; uint8_t segler[4][5] = { {0, 1, 1, 1, 1}, {1, 0, 0, 0, 1}, {0, 0, 0, 0, 1}, {1, 0, 0, 1, 0} }; uint8_t buffer[7][3] = { {1, 1, 1}, {1, 0, 1}, {1, 0, 1}, {0, 0, 0}, {1, 0, 1}, {1, 0, 1}, {1, 1, 1} }; uint8_t kreuz[3][3] = { {0, 1, 0}, {1, 1, 1}, {0, 1, 0} }; uint8_t ship[3][3] = { {1, 1, 0}, {1, 0, 1}, {0, 1, 1} }; /* initialize world with zero (dead cells) */ void clean_world(uint8_t ** world, uint8_t width, uint8_t height) { int a; for (a = 0; a < width; a++) { memset(world[a], 0, height * sizeof(uint8_t)); } } /* allocate memory for world */ uint8_t ** create_world(uint8_t width, uint8_t height) { uint8_t ** world = calloc(width, sizeof(uint8_t *)); int a; for (a = 0; a < width; a++) { world[a] = calloc(height, sizeof(uint8_t)); } clean_world(world, width, height); return world; } void free_world(uint8_t ** world, uint8_t width, uint8_t height) { int a; for (a = 0; a < width; a++) { free(world[a]); } free(world); } /* insert pattern at (x|y) into world */ void inhabit_world(struct pattern pattern, uint8_t x, uint8_t y, uint8_t ** world, uint8_t width, uint8_t height) { uint8_t a, b; for (a = 0; a < pattern.height; a++) { int c = a; if ((y + c) >= height) c -= height; for (b = 0; b < pattern.width; b++) { int d = b; if ((x + d) >= width) d -= width; world[x+d][y+c] = pattern.data[(a*pattern.width)+b]; } } } /* calc alive cells */ uint8_t calc_cell_count(uint8_t ** world, uint8_t width, uint8_t height) { int cell_count = 0; uint8_t a, b; for (a = 0; a < width; a++) { for (b = 0; b < height; b++) { cell_count += (world[a][b]) ? 1 : 0; } } return cell_count; } uint8_t calc_cell_neighbours(uint8_t x, uint8_t y, uint8_t ** world, uint8_t width, uint8_t height) { uint8_t neighbours = 0; int a, b; for (a = x-1; a <= x+1; a++) { int c = a; if (a < 0) c += width; if (a >= width) c -= width; for (b = y-1; b <= y+1; b++) { int d = b; if (a == x && b == y) continue; if (b < 0) d += height; if (b >= height) d -= height; neighbours += (world[c][d] > 0) ? 1 : 0; } } return neighbours; /* 0 <= neighbours <= 8 */ } uint8_t calc_next_cell_gen(uint8_t x, uint8_t y, uint8_t ** world, uint8_t width, uint8_t height) { fflush(stdout); uint8_t neighbours = calc_cell_neighbours(x, y, world, width, height); uint8_t alive = world[x][y]; if (alive) { if (neighbours > 3 || neighbours < 2) { return 0; /* died by over-/underpopulation */ } else { return 1; /* kept alive */ } } else if (neighbours == 3) { return 1; /* born */ } else { return 0; /* still dead */ } } void calc_next_gen(uint8_t ** world, uint8_t ** next_gen, uint8_t width, uint8_t height) { uint8_t x, y; for (x = 0; x < width; x++) { for (y = 0; y < height; y++) { next_gen[x][y] = calc_next_cell_gen(x, y, world, width, height); } } /* copy world */ for (x = 0; x < width; x++) { for (y = 0; y < height; y++) { world[x][y] = next_gen[x][y]; } } } /* print world with colors and count of neighbours */ void print_world(uint8_t ** world, uint8_t width, uint8_t height) { uint8_t x, y; move(0, 0); /* reset cursor */ /* cells */ for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { uint8_t neighbours = calc_cell_neighbours(x, y, world, width, height); if (neighbours > 1) attron(COLOR_PAIR(neighbours)); addch((world[x][y]) ? '0' + neighbours : ' '); if (neighbours > 1) attroff(COLOR_PAIR(neighbours)); } } } #ifdef ENABLE_CURSOR void print_cursor(uint8_t ** world, struct cursor cur) { uint8_t color = (world[cur.x][cur.y]) ? 7 : 6; move(cur.y, cur.x); addch(CURSOR_CHAR | A_BLINK | A_BOLD | A_STANDOUT | COLOR_PAIR(color)); } #endif #ifdef ENABLE_HOTKEYS #define MENU_WIDTH 25 void print_menu(uint8_t width, uint8_t height) { uint8_t startx, starty, mwdth_rel, posy = 0; mwdth_rel = (uint8_t) (MENU_WIDTH)/2+10; startx = (uint8_t) (width/2) - mwdth_rel; starty = (uint8_t) (height/2) - (uint8_t) (mwdth_rel/2); attron(COLOR_PAIR(1)); for (int i = 0; i < MENU_WIDTH; i++) { mvprintw(starty, startx+i, "-"); mvprintw(starty+(mwdth_rel*2/3), startx+i, "-"); if (i % 2 == 0) { mvprintw(starty+posy+1, startx-1, "|"); mvprintw(starty+posy+1, startx+MENU_WIDTH, "|"); posy++; } } mvprintw(starty, startx-1, "+"); mvprintw(starty, startx+MENU_WIDTH, "+"); mvprintw(starty+posy+1, startx-1, "+"); mvprintw(starty+posy+1, startx+MENU_WIDTH, "+"); for (int i = startx; i < startx+MENU_WIDTH; i++) { for (int j = starty+1; j <= starty+posy; j++) { mvprintw(j, i, " "); } } mvprintw(starty+1, startx+1, "q ~ Exit"); mvprintw(starty+3, startx+1, "c ~ Clear Screen"); #if defined(RANDOM_SPAWNS) mvprintw(starty+5, startx+1, "d ~ Rnd Spawns [%d]", (rnd_spawns == 0 ? 0 : 1)); #endif mvprintw(starty+7, startx+1, "p ~ Pause"); mvprintw(starty+9, startx+1, "+/- ~ Change Framerate"); mvprintw(starty+11, startx+1, "0-5 ~ Create Pattern"); attroff(COLOR_PAIR(1)); } #endif /* set up ncurses screen */ WINDOW * init_screen() { WINDOW * win = initscr(); noecho(); timeout(0); keypad(win, 1); mousemask(BUTTON1_PRESSED, NULL); mouseinterval(200); curs_set(0); start_color(); init_color(COLOR_CYAN, 500, 1000, 0); /* redefine as orange */ init_pair(1, COLOR_BLACK, COLOR_WHITE); init_pair(2, COLOR_WHITE, COLOR_BLACK); init_pair(3, COLOR_GREEN, COLOR_BLACK); init_pair(4, COLOR_YELLOW, COLOR_BLACK); init_pair(5, COLOR_CYAN, COLOR_BLACK); init_pair(6, COLOR_BLUE, COLOR_BLACK); init_pair(7, COLOR_MAGENTA, COLOR_BLACK); init_pair(8, COLOR_RED, COLOR_BLACK); return win; } uint8_t **init_world(WINDOW *win, uint8_t **worlds[2], uint8_t *width, uint8_t *height) { getmaxyx(win, *height, *width); for (int i = 0; i < 2; i++) worlds[i] = create_world(*width, *height); return (worlds[0]); } void free_all(uint8_t **worlds[2], uint8_t width, uint8_t height) { free_world(worlds[0], width, height); free_world(worlds[1], width, height); } // returns realloc'd && resized world uint8_t **resized(WINDOW *win, uint8_t **worlds[2], uint8_t *width, uint8_t *height) { free_world(worlds[0], *width, *height); free_world(worlds[1], *width, *height); return (init_world(win, worlds, width, height)); } int main(int argc, char * argv[]) { WINDOW * win = init_screen(); #ifdef ENABLE_CURSOR MEVENT event; #endif /* predefined patterns */ struct pattern patterns[] = { {3, 3, (uint8_t *) start}, {3, 3, (uint8_t *) glider}, {5, 4, (uint8_t *) segler}, {3, 7, (uint8_t *) buffer}, {8, 4, (uint8_t *) kreuz}, {6, 6, (uint8_t *) ship} }; struct cursor cur = {0, 0}; struct config cfg; int generation = 0, input, framerate = 17; uint8_t width, height; uint8_t ** worlds[2], ** world; #if defined(RANDOM_SPAWNS) int idle_gens = 0; srand(time(NULL)); #endif memset(&cfg, '\0', sizeof(struct config)); /* initialize world */ world = init_world(win, worlds, &width, &height); /* make the world real */ inhabit_world(patterns[3], width/2, height/2, worlds[0], width, height); /* simulation loop */ while(!cfg.quit) { if (!cfg.paused) { /* calc next generation */ usleep(1 / (float) framerate * 1000000); /* sleep */ calc_next_gen(world, worlds[++generation % 2], width, height); world = worlds[generation % 2]; /* new world */ } /* handle events */ switch (input = getch()) { #ifdef ENABLE_HOTKEYS case '+': /* increase framerate */ framerate++; break; case '-': /* decrease framerate */ if (framerate > 1) framerate--; break; case 'q': /* quit */ cfg.quit = 1; break; #if defined(RANDOM_SPAWNS) case 'd': /* disable random spawn */ rnd_spawns = ~rnd_spawns; break; #endif case 'p': /* pause */ cfg.paused ^= 1; break; case 'c': /* clean world */ clean_world(world, width, height); generation = 0; break; #endif #if defined(ENABLE_HOTKEYS) || defined(ENABLE_CURSOR) case '0': /* insert pattern */ case '1': case '2': case '3': case '4': case '5': inhabit_world(patterns[input - '0'], cur.x, cur.y, world, width, height); break; #endif #ifdef ENABLE_CURSOR case ' ': /* toggle cell at cursor position */ world[cur.x][cur.y] = (world[cur.x][cur.y]) ? 0 : 1; break; case KEY_MOUSE: /* move cursor to mouse posititon */ if (getmouse(&event) == OK && event.bstate & BUTTON1_PRESSED) { cur.x = event.x; cur.y = event.y; if (cur.x >= width) cur.x = width - 1; if (cur.y >= height) cur.y = height - 1; world[cur.x][cur.y] = (world[cur.x][cur.y]) ? 0 : 1; } break; case KEY_UP: if (cur.y > 0) { cur.y--; } break; case KEY_DOWN: if (cur.y < height-1) { cur.y++; } break; case KEY_LEFT: if (cur.x > 0) { cur.x--; } break; case KEY_RIGHT: if (cur.x < width-1) { cur.x++; } break; case KEY_RESIZE: world = resized(win, worlds, &width, &height); break; #endif } #if defined(RANDOM_SPAWNS) if (rnd_spawns) { /* spawn a new pattern at a random position, if nothing happens */ idle_gens++; if (idle_gens >= RANDOM_SPAWNS && !cfg.paused) { idle_gens = 0; inhabit_world(patterns[rand() % (sizeof(patterns)/sizeof(patterns[0]))], rand() % (width - 1), rand() % (height - 1), world, width, height); } } #endif /* update screen */ print_world(world, width, height); #ifdef ENABLE_CURSOR print_cursor(world, cur); #endif #ifdef ENABLE_STATUS attron(COLOR_PAIR(1)); for (int i = 0; i < width; i++) mvprintw(0, width - i, " "); mvprintw(0, 0, "[generation:%4d] [cells:%3d] [fps:%2d] [width:%d] [height:%d] [cursor:%2d|%2d]", generation, calc_cell_count(world, width, height), framerate, width, height, cur.x, cur.y); if (cfg .paused) mvprintw(0, width-6, "PAUSED"); attroff(COLOR_PAIR(1)); #endif #ifdef ENABLE_HOTKEYS if (cfg.paused) { print_menu(width, height); usleep(1); } #endif refresh(); } free_all(worlds, width, height); delwin(win); endwin(); /* exit ncurses mode */ return (EXIT_SUCCESS); }