/* * Simple(?) text-based implementation of Conway's game of life. */ #include #include #include #include #include "ncurses-util.h" #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) #define MAX(a, b) ((a) > (b)) ? (a) : (b) /* * tables describing keyboard-activated functions -- characters to activate, * descriptions for help, and functions to be called when invoked. * * functions return true if mode (main or edit) should continue, false * otherwise. */ typedef bool (*CommandFunction)(int ch); typedef struct { int key_char; char *help; CommandFunction fcn; } command_t; bool quit_command(int unused); bool clear_command(int unused); bool edit_command(int unused); bool step_command(int unused); bool move_cursor(int ch); bool toggle_cell(int unused); /* returns index of command matching 'ch', or -1 if not found */ int command_lookup(command_t table[], size_t table_size, int ch); command_t main_commands[] = { { 'c', "c to clear board", clear_command }, { 'e', "e to edit board", edit_command }, { 's', "s to step", step_command }, { 'q', "q to quit", quit_command} }; command_t edit_commands[] = { { KEY_UP, "arrow keys to move cursor", move_cursor }, { KEY_DOWN, "arrow keys to move cursor", move_cursor }, { KEY_LEFT, "arrow keys to move cursor", move_cursor }, { KEY_RIGHT, "arrow keys to move cursor", move_cursor }, { ' ', "space to toggle cell", toggle_cell }, { 'x', "x to stop editing", quit_command } }; /* * functions and variables for help */ void initialize_help(command_t table[], size_t table_size, char *** help_lines_p); int max_length(char *lines[]); void show_help(char *help_lines[]); /* NULL to indicate end of list */ char *empty_help[] = { NULL }; char **main_help = empty_help; char **edit_help = empty_help; /* * data structure for "board" * * cells are kept as a 1D array, with macros to access by row and column, * because of how awkward 2D arrays can be in C. * * rows and cols include a boundary that's always false and not displayed */ typedef struct { int rows; int cols; bool *cells; /* current generation */ bool *new_cells; /* next generation */ } board_t; #define CELL(r, c) board.cells[(r)*board.cols + (c)] #define NEW_CELL(r, c) board.new_cells[(r)*board.cols + (c)] bool make_board(int rows, int cols); void free_board(void); void clear_board(void); void show_board(); void edit_board(void); void step_board(void); board_t board = { -1, -1, NULL }; int cursor_row = -1; int cursor_col = -1; /* * screen elements */ WINDOW* side_window; WINDOW* board_window; /* * main program */ int main(void) { /* initialize */ ncurses_start(); initialize_help(main_commands, ARRAY_SIZE(main_commands), &main_help); initialize_help(edit_commands, ARRAY_SIZE(edit_commands), &edit_help); int side_window_width = MAX(max_length(main_help), max_length(edit_help)) + 2; int brows = LINES; int bcols = (COLS - side_window_width) / 2; if (!make_board(brows, bcols)) { ncurses_end(); return EXIT_FAILURE; } clear_board(); side_window = newwin(LINES, side_window_width, 0, bcols*2); board_window = newwin(LINES, bcols*2, 0, 0); refresh(); /* main processing loop */ show_help(main_help); show_board(); bool done = false; while (!done) { int ch = getch(); int index = command_lookup(main_commands, ARRAY_SIZE(main_commands), ch); if (index >= 0) { done = !main_commands[index].fcn(ch); } } /* clean up and exit */ if (main_help != empty_help) free(main_help); if (edit_help != empty_help) free(edit_help); free_board(); delwin(side_window); delwin(board_window); ncurses_end(); return EXIT_SUCCESS; } /* * function definitions */ int max_length(char *lines[]) { int tmp = 0; for (int i = 0; lines[i] != NULL; ++i) { tmp = MAX(tmp, strlen(lines[i])); } return tmp; } void initialize_help(command_t table[], size_t table_size, char *** help_lines_p) { /* FIXME? allocate maximum size -- a bit sloppy but simpler code */ char **temp = malloc(sizeof(*help_lines_p[0]) * (table_size+1)); if (temp != NULL) { int j = 0; /* index into table being built */ for (int i = 0; i < table_size; ++i) { if ((i == 0) || (strcmp(table[i-1].help, table[i].help) != 0)) { temp[j++] = table[i].help; } } temp[j] = NULL; *help_lines_p = temp; } } void show_help(char *help_lines[]) { wclear(side_window); for (int i = 0; help_lines[i] != NULL; ++i) { wmove(side_window, i, 1); wprintw(side_window, "%s", help_lines[i]); } box(side_window, 0, 0); wrefresh(side_window); } bool make_board(int rows, int cols) { board.rows = rows; board.cols = cols; board.cells = malloc(sizeof(board.cells[0]) * board.rows * board.cols); board.new_cells = malloc(sizeof(board.cells[0]) * board.rows * board.cols); if ((board.cells == NULL) || (board.new_cells == NULL)) { return false; } return true; } void free_board(void) { free(board.cells); free(board.new_cells); } void clear_board(void ) { for (int r = 0; r < board.rows; ++r) { for (int c = 0; c < board.cols; ++c) { CELL(r,c) = false; NEW_CELL(r,c) = false; } } } void show_cell(int row, int col, char ch) { wmove(board_window, row, col*2); waddch(board_window, ch); wmove(board_window, row, col*2 + 1); waddch(board_window, ch); } void show_board(void) { wclear(board_window); if (board.cells != NULL) { for (int r = 1; r < board.rows-1; ++r) { for (int c = 1; c < board.cols-1; ++c) { if (CELL(r,c)) wattron(board_window, A_REVERSE); if ((r == cursor_row) && (c == cursor_col)) show_cell(r, c, '_'); else show_cell(r, c, ' '); wattroff(board_window, A_REVERSE); } } } box(board_window, 0, 0); wrefresh(board_window); } void edit_board(void) { show_help(edit_help); cursor_row = 1; cursor_col = 1; show_board(); bool done = false; while (!done) { int ch = getch(); int index = command_lookup(edit_commands, ARRAY_SIZE(edit_commands), ch); if (index >= 0) { done = !edit_commands[index].fcn(ch); } } cursor_row = -1; cursor_col = -1; } void step_board(void) { if (board.cells != NULL) { /* border cells are always empty */ for (int r = 1; r < board.rows-1; ++r) { for (int c = 1; c < board.cols-1; ++c) { int nbrs = 0; if (CELL(r-1,c-1)) ++nbrs; if (CELL(r-1,c)) ++nbrs; if (CELL(r-1,c+1)) ++nbrs; if (CELL(r,c-1)) ++nbrs; if (CELL(r,c+1)) ++nbrs; if (CELL(r+1,c-1)) ++nbrs; if (CELL(r+1,c)) ++nbrs; if (CELL(r+1,c+1)) ++nbrs; NEW_CELL(r,c) = (CELL(r, c) && ((nbrs == 2) || (nbrs == 3))) || (!CELL(r, c) && (nbrs == 3)); } } } bool *temp = board.cells; board.cells = board.new_cells; board.new_cells = temp; show_board(); } bool quit_command(int unused) { return false; } bool clear_command(int unused) { clear_board(); show_board(); return true; } bool edit_command(int unused) { edit_board(); show_board(); show_help(main_help); return true; } bool step_command(int unused) { step_board(); show_board(); return true; } bool move_cursor(int ch) { switch (ch) { case KEY_UP: if (cursor_row > 1) --cursor_row; break; case KEY_DOWN: if (cursor_row < board.rows-1) ++cursor_row; break; case KEY_LEFT: if (cursor_col > 1) --cursor_col; break; case KEY_RIGHT: if (cursor_col < board.cols-1) ++cursor_col; break; default: break; } show_board(); return true; } bool toggle_cell(int unused) { CELL(cursor_row, cursor_col) = !CELL(cursor_row, cursor_col); show_board(); return true; } int command_lookup(command_t table[], size_t table_size, int ch) { for (size_t i = 0; i < table_size; ++i) { if (ch == table[i].key_char) return i; } return -1; }