gol

Implementation of Conway's Game of Life writen in C
git clone git://git.dimitrijedobrota.com/gol.git
Log | Files | Refs | README

commit 4c83da5de9856f5b83d30567244a718f9b56fa4d
parent 7681e2df2437464549b9e6144beb5d7bd837671b
Author: Dimitrije Dobrota <mail@dimitrijedobrota.com>
Date:   Mon, 23 May 2022 18:44:19 +0200

Integration with the logic module

- All game state is handled by logic.c
- Improved gameplay:
  - Pause/Play
  - Quit
  - Speed control
  - Cell toggle with a movable cursor
- Improved game view:
  - Player area is now outlined
  - Unicode characters work on Windows Terminal
  - Self centering and resizeing subwindow
- Improved game framerate:
  - Constant time between each update
  - Smoother movement

Diffstat:
Minclude/display.h | 8++++++--
Minclude/file.h | 2+-
Minclude/logic.h | 7++++---
Minclude/utils.h | 9+++++++--
Minclude/window.h | 1+
Msrc/display.c | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Msrc/file.c | 2+-
Msrc/logic.c | 14++++++++------
Msrc/main.c | 258+++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/window.c | 114++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
10 files changed, 292 insertions(+), 200 deletions(-)

diff --git a/include/display.h b/include/display.h @@ -2,13 +2,14 @@ #define DISPLAY_H #include "window.h" +#include "logic.h" extern window_T MAIN_w; typedef int (*input_f)(int); struct menu_T { - void (*callback)(window_T, char *); + void (*callback)(window_T, char *, int); char *name; }; @@ -25,7 +26,10 @@ int display_start(void); int display_stop(void); void display_menu(window_T win, struct menu_T *items, int size); -int display_imenu(window_T wind, struct imenu_T *items, int size); +int display_imenu(window_T wind, struct imenu_T *items, int size); +void display_game(window_T wind, cell **mat, int w, int h, int screen_offset_y, + int screen_offset_x, int *cursor_offset_y, + int *cursor_offset_x); void handle_winch(int sig); diff --git a/include/file.h b/include/file.h @@ -8,6 +8,6 @@ void load_files(void); void free_files(void); int file_select(char *ext, char ***buffer); -void load_file(window_T win, char *pass); +void load_file(window_T win, char *pass, int index); #endif diff --git a/include/logic.h b/include/logic.h @@ -4,11 +4,13 @@ #define cell unsigned char extern cell **mat; -extern char **evolution_names[]; +extern char *evolution_names[]; +extern int evolution_cells[]; +extern int evolution_size; int logic_init(int w, int h); int evolution_init(int index); void do_evolution(int steps); int logic_free(void); -#endif -\ No newline at end of file +#endif diff --git a/include/utils.h b/include/utils.h @@ -5,7 +5,8 @@ #define MAX(a, b) ((a > b) ? a : b) #define MIN(a, b) ((a < b) ? a : b) -#define CLAMP(a, x, y) (a = MAX(x, MIN(a, y))) +#define CLAMP(a, x, y) ((a) = (MAX(x, MIN(a, y)))) +#define ACLAMP(a, x, y) (MAX(x, MIN(a, y))) #ifdef _WIN32 #define is_term_resized(a, b) is_termresized() @@ -17,6 +18,10 @@ goto redraw; \ } #else -#define HANDLE_RESIZE goto redraw; +#define HANDLE_RESIZE \ + { \ + handle_winch(10); \ + goto redraw; \ + } #endif #endif diff --git a/include/window.h b/include/window.h @@ -10,6 +10,7 @@ T window_new(void); void window_free(T self); T window_init(T self); T window_split(T self, int hor, int a, int b); +T window_center(T self, int tmp, int h, int w); void window_update_children(T self); T window_sibiling(T self); diff --git a/src/display.c b/src/display.c @@ -4,12 +4,18 @@ #include <string.h> #include "display.h" +#include "logic.h" #include "utils.h" #include "window.h" #define center_vertical(n) wcenter_vertical(MAIN_W, n); #define center_horizontal(y, n) wcenter_horizontal(MAIN_W, y, n); +#define CHAR_CIRCLE L'\u26AB' +#define CHAR_SQUARE L'\u2B1B' +#define CHAR_SQUARED L'\u2B1C' +#define CHAR_CIRCLE_DOT L'\u2299' + window_T MAIN_w = NULL; int input(WINDOW *win, char *buffer, int size, input_f crit) { @@ -59,7 +65,8 @@ int display_imenu(window_T wind, struct imenu_T *items, int size) { int current = 0; for (int i = 0; i < size; i++) - items[i].buffer = calloc(5, sizeof(char)); + if (!items[i].buffer) + items[i].buffer = calloc(5, sizeof(char)); int maxi = 0, len = 0; for (int i = 0; i < size; i++) @@ -146,7 +153,7 @@ redraw:; } else if (c == '\n') { wattrset(win, COLOR_PAIR(0)); window_clear(wind); - items[current].callback(wind, items[current].name); + items[current].callback(wind, items[current].name, current); /* window_clear(wind); */ return; } else if (c == 27) @@ -159,8 +166,56 @@ redraw:; } } +#define y_at(y) (y + screen_offset_y + h) % h + 1 +#define x_at(x) (x + screen_offset_x + w) % w + 1 + +void display_game(window_T wind, cell **mat, int w, int h, int screen_offset_y, + int screen_offset_x, int *cursor_offset_y, + int *cursor_offset_x) { + WINDOW *win = window_win(wind); + wattrset(win, COLOR_PAIR(0)); + + int ph = window_height(wind), pw = window_wight(wind) / 2; + +#ifdef _WIN32 + window_clear(wind); +#endif + for (int i = 0; i < ph; i++) { + wmove(win, i + 1, 1); + for (int j = 0; j < pw; j++) { + wattrset(win, COLOR_PAIR((mat[y_at(i)][x_at(j)]) + 2)); + + if (mat[y_at(i)][x_at(j)]) + waddstr(win, "\u26AB"); + else + waddstr(win, " "); + } + } + + CLAMP(*cursor_offset_y, 0, ph - 1); + CLAMP(*cursor_offset_x, 0, pw - 1); + + wmove(win, *cursor_offset_y + 1, *cursor_offset_x * 2 + 1); + wattrset(win, COLOR_PAIR( + (mat[y_at(*cursor_offset_y)][x_at(*cursor_offset_x)]) + 5)); + + if (mat[y_at(*cursor_offset_y)][x_at(*cursor_offset_x)]) + waddstr(win, "\u26AB"); + else + waddstr(win, " "); + + wrefresh(win); +} + +#define LMAX 8 +#define LEFT 0 +#define CENTER 1 +#define RIGHT 2 + void curses_start(void) { initscr(); + window_settings(stdscr); + start_color(); use_default_colors(); @@ -168,13 +223,16 @@ void curses_start(void) { noecho(); nodelay(stdscr, 1); - window_settings(stdscr); - init_pair(0, COLOR_WHITE, -1); init_pair(1, COLOR_RED, -1); - init_pair(2, COLOR_WHITE, COLOR_WHITE); - init_pair(3, COLOR_BLACK, -1); + init_pair(2, COLOR_WHITE, -1); + init_pair(3, COLOR_WHITE, -1); + init_pair(4, COLOR_RED, -1); + + init_pair(5, COLOR_WHITE, COLOR_CYAN); + init_pair(6, COLOR_WHITE, COLOR_CYAN); + init_pair(7, COLOR_RED, COLOR_CYAN); } void curses_stop(void) { @@ -187,15 +245,14 @@ void handle_winch(int sig) { refresh(); clear(); - refresh(); window_init(MAIN_w); window_update_children(MAIN_w); } int display_start(void) { -#ifndef _WIN32 - signal(SIGWINCH, handle_winch); -#endif + /* #ifndef _WIN32 */ + /* signal(SIGWINCH, handle_winch); */ + /* #endif */ curses_start(); MAIN_w = window_init(window_new()); diff --git a/src/file.c b/src/file.c @@ -132,4 +132,4 @@ int file_select(char *ext, char ***buffer) { void load_files(void) { loaded_files = file_fromDirectory(); } void free_files(void) { file_free(loaded_files); } -void load_file(window_T win, char *pass) { (void)3; } +void load_file(window_T win, char *pass, int index) { (void)3; } diff --git a/src/logic.c b/src/logic.c @@ -5,11 +5,11 @@ #include "utils.h" cell **mat; -char **evolution_names[] = {"Normal", "CoExsistance", "Predator", "Virus", - "Unknown"}; +char *evolution_names[] = {"Normal", "CoExsistance", "Predator", "Virus", + "Unknown"}; +int evolution_cells[] = {2, 3, 3, 3, 3}; +int evolution_size = 5; -static void (*evolution_modes[])() = { - evolveNormal, evolveCoExist, evolvePredator, evolveVirus, evolveUnknown}; static void (*evolve)(void); static int height, width; static int mod; @@ -225,6 +225,9 @@ int logic_init(int w, int h) { return 1; } +static void (*evolution_modes[])() = { + evolveNormal, evolveCoExist, evolvePredator, evolveVirus, evolveUnknown}; + int evolution_init(int index) { evolve = evolution_modes[index]; return 1; @@ -235,4 +238,4 @@ int logic_free(void) { free(mat[i]); free(mat); return 1; -} -\ No newline at end of file +} diff --git a/src/main.c b/src/main.c @@ -1,7 +1,3 @@ -// This is not a final version of the code -// While I've cleaned this file up, -// all of the game logic is still here - #include <ctype.h> #include <curses.h> #include <locale.h> @@ -10,154 +6,142 @@ #include <unistd.h> #include <wchar.h> +#include <time.h> + #include "display.h" #include "file.h" +#include "logic.h" #include "utils.h" #include "window.h" -extern window_T MAIN_w; - -WINDOW *game_w, *board_w, *status_w; -int top_space = 5; - -void show(window_T wind, unsigned **univ, int w, int h, int y, int x) { - WINDOW *win = window_win(wind); - wattrset(win, COLOR_PAIR(0)); - - int ph = h, pw = w, mh, mw; - mh = window_height(wind); - mw = window_wight(wind) / 2; - CLAMP(ph, 0, mh); - CLAMP(pw, 0, mw); - - int top = wcenter_vertical(wind, ph); - for (int i = 0; i < ph; i++) { - wcenter_horizontal(wind, top + i, pw * 2); - for (int j = 0; j < pw; j++) { #ifdef _WIN32 - wattrset(win, - COLOR_PAIR((univ[(i + y + h) % h][(j + x + w) % w]) ? 2 : 3)); - wprintw(win, " "); +#define TIME_CONST 100 #else - if (univ[(i + y + h) % h][(j + x + w) % w]) - wprintw(win, "%lc ", (wchar_t)L'\u23FA'); - else - wprintw(win, " "); - /* wprintw(win, "%lc", (wchar_t)L'\u2B1B'); */ - /* wprintw(win, " ", (wchar_t)L'\u2B1C'); */ +#define TIME_CONST 100000 #endif - } - } - wrefresh(win); -} - -void evolve(unsigned **univ, unsigned **new, int w, int h, int step) { - do { - for (int i = 0; i < h; i++) - for (int j = 0; j < w; j++) { - int n = 0; - for (int y1 = i - 1; y1 <= i + 1; y1++) - for (int x1 = j - 1; x1 <= j + 1; x1++) - if (univ[(y1 + h) % h][(x1 + w) % w]) - n++; - - if (univ[i][j]) - n--; - new[i][j] = (n == 3 || (n == 2 && univ[i][j])); - } - unsigned **t = univ; - univ = new; - new = t; - } while (--step); - unsigned **t = univ; - univ = new; - new = t; - - for (int i = 0; i < h; i++) - for (int j = 0; j < w; j++) - univ[i][j] = new[i][j]; -} - -void game(window_T wind, int w, int h) { - if (w <= 0 || h <= 0) { - h = window_height(wind); - w = window_wight(wind); - w /= 2; - } - unsigned **univ; - univ = malloc(h * sizeof(unsigned *)); - for (int i = 0; i < h; i++) { - univ[i] = malloc(w * sizeof(unsigned)); - for (int j = 0; j < w; j++) - univ[i][j] = rand() < RAND_MAX / 7 ? 1 : 0; - } +extern window_T MAIN_w; +extern cell **mat; +extern char *evolution_names[]; +extern int evolution_cells[]; +extern int evolution_size; - unsigned **new = malloc(h * sizeof(unsigned *)); - for (int i = 0; i < h; i++) { - new[i] = malloc(w * sizeof(unsigned)); - } +window_T status_w, screen_w, game_w; +int top_space = 5; - int oy = 0, ox = 0; +void game(window_T wind, int w, int h, int ncells) { + int screen_offset_y = 0, screen_offset_x = 0; + int cursor_offset_y = 0, cursor_offset_x = 0; int in = 3; int gen_step = 1; + int play = 0; + int gen = 1; window_clear(wind); + game_w = window_center(wind, 0, h, w * 2); + + clock_t start_t, end_t = 0, total_t; redraw:; - /* window_clear(wind); */ int CLINES = LINES, CCOLS = COLS; + while (TRUE) { - show(wind, univ, w, h, oy, ox); - evolve(univ, new, w, h, gen_step); - for (int i = 0; i < in; i++) { + start_t = clock(); + display_game(game_w, mat, w, h, screen_offset_y, screen_offset_x, + &cursor_offset_y, &cursor_offset_x); + if (play) { + do_evolution(gen_step); + gen++; + } + + while ((total_t = (long int)(end_t - start_t)) < TIME_CONST) { + refresh(); int c = getch(); switch (c) { + case 'p': + case 'P': + play = !play; + break; + case 27: + return; + case '+': + gen_step++; + break; + case '-': + gen_step--; + break; case KEY_A1: case KEY_C1: case KEY_END: case KEY_HOME: case KEY_LEFT: - ox--; + screen_offset_x--; break; case KEY_A3: case KEY_C3: case KEY_NPAGE: case KEY_PPAGE: case KEY_RIGHT: - ox++; + screen_offset_x++; break; } + switch (c) { case KEY_A1: case KEY_A3: case KEY_HOME: case KEY_PPAGE: case KEY_UP: - oy--; + screen_offset_y--; break; case KEY_C1: case KEY_C3: case KEY_DOWN: case KEY_END: case KEY_NPAGE: - oy++; + screen_offset_y++; } - break; - switch (c) { - case 27: - return; + + if (!play) { + switch (c) { + case 'w': + case 'W': + cursor_offset_y--; + break; + case 's': + case 'S': + cursor_offset_y++; + break; + case 'a': + case 'A': + cursor_offset_x--; + break; + case 'd': + case 'D': + cursor_offset_x++; + break; + case ' ': + mat[(cursor_offset_y + screen_offset_y + h) % h + 1] + [(cursor_offset_x + screen_offset_x + w) % w + 1] = + (mat[(cursor_offset_y + screen_offset_y + h) % h + 1] + [(cursor_offset_x + screen_offset_x + w) % w + 1] + + 1) % + ncells; + break; + } + } + screen_offset_x = (screen_offset_x + w) % w; + screen_offset_y = (screen_offset_y + h) % h; + CLAMP(gen_step, 1, 10); + + /* usleep(100000); */ + if (is_term_resized(CLINES, CCOLS)) { + flushinp(); + HANDLE_RESIZE; + goto redraw; } + end_t = clock(); // clock stopped } - ox = (ox + w) % w; - oy = (oy + h) % h; - flushinp(); - - usleep(200000); - if (is_term_resized(CLINES, CCOLS)) { - HANDLE_RESIZE; - goto redraw; - } } } @@ -167,18 +151,43 @@ struct imenu_T imenu_items[] = { }; int imenu_items_s = sizeof(imenu_items) / sizeof(struct imenu_T); -void settings(window_T wind, char *pass) { - if (display_imenu(wind, imenu_items, imenu_items_s)) { - int row = atoi(imenu_items[0].buffer); - int column = atoi(imenu_items[1].buffer); - game(wind, row, column); +void settings(window_T wind, char *pass, int index) { + while (TRUE) { + if (display_imenu(wind, imenu_items, imenu_items_s)) { + int row = atoi(imenu_items[0].buffer); + int column = atoi(imenu_items[1].buffer); + + if (!row || !column) + continue; + + logic_init(column, row); + evolution_init(index); + + game(wind, row, column, evolution_cells[index]); + break; + } else { + break; + } } - for (int i = 0; i < imenu_items_s; i++) + for (int i = 0; i < imenu_items_s; i++) { free(imenu_items[i].buffer); + imenu_items[i].buffer = NULL; + } +} + +void mode_select(window_T wind, char *pass, int index) { + struct menu_T *mode_items = malloc(evolution_size * sizeof(struct menu_T)); + for (int i = 0; i < evolution_size; i++) { + mode_items[i].name = evolution_names[i]; + mode_items[i].callback = settings; + } + display_menu(wind, mode_items, evolution_size); + + free(mode_items); } -void load(window_T wind, char *pass) { +void load(window_T wind, char *pass, int index) { char **buffer; int n; @@ -193,55 +202,44 @@ void load(window_T wind, char *pass) { free(buffer); display_menu(wind, file_items, n); - game(wind, 0, 0); + mode_select(wind, "nothing", 0); free(file_items); free_files(); } -void exitp(window_T wind, char *pass) { +void exitp(window_T wind, char *pass, int index) { display_stop(); exit(0); } -struct menu_T mode_items[] = { - {settings, "Normalan"}, - {settings, "Koegzistencija"}, - {settings, "Predator i pljen"}, - {settings, "Virus"}, - {settings, "Nepoznanost"}, -}; -int mode_items_s = sizeof(mode_items) / sizeof(struct menu_T); - -void mode_select(window_T wind, char *pass) { - display_menu(wind, mode_items, mode_items_s); -} - struct menu_T menu_items[] = { {mode_select, "Start"}, { load, "Load"}, { exitp, "Exit"} }; + int menu_items_s = sizeof(menu_items) / sizeof(struct menu_T); int state = 0; int main(void) { setlocale(LC_ALL, ""); + if (!display_start()) { printf("Couldn't start the display!\n"); } + if (!file_setup()) { printf("File setup error\n"); } - T t1, t2; - t1 = window_split(MAIN_w, 1, 5, 0); - t2 = window_sibiling(t1); + status_w = window_split(MAIN_w, 1, 3, 0); + screen_w = window_sibiling(status_w); while (TRUE) { - display_menu(t2, menu_items, menu_items_s); - window_clear(t2); + display_menu(screen_w, menu_items, menu_items_s); + window_clear(screen_w); } if (!display_stop()) { diff --git a/src/window.c b/src/window.c @@ -2,6 +2,7 @@ #include <stdlib.h> #include <string.h> +#include "utils.h" #include "window.h" #define H(c) c->param[0] @@ -52,6 +53,7 @@ int window_wight(T self) { return W(self) - 2; } WINDOW *window_win(T self) { return self->win; } T window_init(T self) { + self->win = stdscr; box(self->win, ACS_VLINE, ACS_HLINE); wrefresh(self->win); @@ -69,54 +71,61 @@ void window_free(T self) { free(self); } -void window_calc(T self) { +void window_calc_children(T self) { T c1, c2, f, nf; c1 = self->c1; c2 = self->c2; int fixed = 0, *fv; int hor = self->mod[0], a = self->mod[1], b = self->mod[2]; - if (a == 0 && b == 0) - return; + if (c2 != NULL) { + if (a == 0 && b == 0) + return; + + if (a == 0) { + f = c2; + nf = c1; + fv = &b; + fixed = 1; + } else if (b == 0) { + f = c1; + nf = c2; + fv = &a; + fixed = 1; + } - if (a == 0) { - f = c2; - nf = c1; - fv = &b; - fixed = 1; - } else if (b == 0) { - f = c1; - nf = c2; - fv = &a; - fixed = 1; - } + if (hor) { + W(c1) = W(c2) = W(self) - 2; - if (hor) { - W(c1) = W(c2) = W(self) - 2; + if (!fixed) { + H(c1) = ((H(self) - 2) / (a + b)) * a; + } else { + H(f) = *fv; + } + H(nf) = (H(self) - 2) - H(f); - if (!fixed) { - H(c1) = ((H(self) - 2) / (a + b)) * a; + X(c1) = X(c2) = X(self) + 1; + Y(c1) = Y(self) + 1; + Y(c2) = Y(self) + H(c1) + 1; } else { - H(f) = *fv; + H(c1) = H(c2) = H(self) - 2; + + if (!fixed) { + W(c1) = ((W(self) - 2) / (a + b)) * a; + } else { + W(f) = *fv; + } + W(nf) = (W(self) - 2) - W(f); + + Y(c1) = Y(c2) = Y(self) + 1; + X(c1) = X(self) + 1; + X(c2) = X(self) + W(c1) + 1; } - H(nf) = (H(self) - 2) - H(f); - - X(c1) = X(c2) = X(self) + 1; - Y(c1) = Y(self) + 1; - Y(c2) = Y(self) + H(c1) + 1; } else { - H(c1) = H(c2) = H(self) - 2; - - if (!fixed) { - W(c1) = ((W(self) - 2) / (a + b)) * a; - } else { - W(f) = *fv; - } - W(nf) = (W(self) - 2) - W(f); - - Y(c1) = Y(c2) = Y(self) + 1; - X(c1) = X(self) + 1; - X(c2) = X(self) + W(c1) + 1; + H(c1) = ACLAMP(a + 2, 0, H(self)); + W(c1) = ACLAMP(b + 2, 0, W(self)); + Y(c1) = Y(self) + (H(self) - H(c1)) / 2; + X(c1) = X(self) + (W(self) - W(c1)) / 2; } } @@ -124,9 +133,6 @@ T window_split(T self, int hor, int a, int b) { self->c1 = window_new(); self->c2 = window_new(); - self->c1 = window_new(); - self->c2 = window_new(); - self->c1->sibiling = self->c2; self->c2->sibiling = self->c1; @@ -134,7 +140,7 @@ T window_split(T self, int hor, int a, int b) { self->mod[1] = a; self->mod[2] = b; - window_calc(self); + window_calc_children(self); WINDOW_new(self->c1); WINDOW_new(self->c2); @@ -142,17 +148,35 @@ T window_split(T self, int hor, int a, int b) { return self->c1; } +T window_center(T self, int tmp, int h, int w) { + self->c1 = window_new(); + self->c2 = NULL; + + self->mod[0] = tmp; + self->mod[1] = h; + self->mod[2] = w; + + window_calc_children(self); + WINDOW_new(self->c1); + + return self->c1; +} + void window_update_children(T self) { - if (self == NULL || self->c1 == NULL || self->c2 == NULL) + if (self == NULL || self->c1 == NULL) return; + window_calc_children(self); + delwin(self->c1->win); - delwin(self->c2->win); - window_calc(self); WINDOW_new(self->c1); - WINDOW_new(self->c2); window_update_children(self->c1); - window_update_children(self->c2); + + if (self->c2 != NULL) { + delwin(self->c2->win); + WINDOW_new(self->c2); + window_update_children(self->c2); + } } void window_clear(T self) {