home · contact · privacy
Added a gameplay recording system and restructured everything to make it fit in.
[plomrogue] / roguelike.c
1 #include <stdlib.h>
2 #include <limits.h>
3 #include <stdint.h>
4 #include <ncurses.h>
5 #include <string.h>
6 #include <time.h>
7 #include <unistd.h>
8 #include "windows.h"
9 #include "draw_wins.h"
10 #include "roguelike.h"
11 #include "keybindings.h"
12
13 uint16_t rrand(char use_seed, uint32_t new_seed) {
14 // Pseudo-random number generator (LGC algorithm). Use instead of rand() to ensure portable predictability.
15   static uint32_t seed;
16   if (0 != use_seed)
17     seed = new_seed;
18   seed = ((seed * 1103515245) + 12345) % 2147483648;   // Values as recommended by POSIX.1-2001 (see rand(3)).
19   return (seed / 65536); }                         // Ignore least significant 16 bits (they are less random).
20
21 uint16_t read_uint16_bigendian(FILE * file) {
22 // Read uint16 from file in big-endian order.
23   const uint16_t nchar = UCHAR_MAX + 1;
24   unsigned char a = fgetc(file);
25   unsigned char b = fgetc(file);
26   return (a * nchar) + b; }
27
28 void write_uint16_bigendian(uint16_t x, FILE * file) {
29 // Write uint16 to file in beg-endian order.
30   const uint16_t nchar = UCHAR_MAX + 1;
31   unsigned char a = x / nchar;
32   unsigned char b = x % nchar;
33   fputc(a, file);
34   fputc(b, file); }
35
36 uint32_t read_uint32_bigendian(FILE * file) {
37 // Read uint32 from file in big-endian order.
38   const uint16_t nchar = UCHAR_MAX + 1;
39   unsigned char a = fgetc(file);
40   unsigned char b = fgetc(file);
41   unsigned char c = fgetc(file);
42   unsigned char d = fgetc(file);
43   return (a * nchar * nchar * nchar) + (b * nchar * nchar) + (c * nchar) + d; }
44
45 void write_uint32_bigendian(uint32_t x, FILE * file) {
46 // Write uint32 to file in beg-endian order.
47   const uint16_t nchar = UCHAR_MAX + 1;
48   unsigned char a = x / (nchar * nchar * nchar);
49   unsigned char b = (x - (a * nchar * nchar * nchar)) / (nchar * nchar);
50   unsigned char c = (x - ((a * nchar * nchar * nchar) + (b * nchar * nchar))) / nchar;
51   unsigned char d = x % nchar;
52   fputc(a, file);
53   fputc(b, file);
54   fputc(c, file);
55   fputc(d, file); }
56
57 void load_game(struct World * world) {
58 // Load game data from game file.
59   FILE * file = fopen("savefile", "r");
60   world->seed = read_uint32_bigendian(file);
61   world->turn = read_uint32_bigendian(file);
62   world->player->y = read_uint16_bigendian(file);
63   world->player->x = read_uint16_bigendian(file);
64   world->monster->y = read_uint16_bigendian(file);
65   world->monster->x = read_uint16_bigendian(file);
66   fclose(file); }
67
68 void save_game(struct World * world) {
69 // Save game data to game file.
70   FILE * file = fopen("savefile", "w");
71   write_uint32_bigendian(world->seed, file);
72   write_uint32_bigendian(world->turn, file);
73   write_uint16_bigendian(world->player->y, file);
74   write_uint16_bigendian(world->player->x, file);
75   write_uint16_bigendian(world->monster->y, file);
76   write_uint16_bigendian(world->monster->x, file);
77   fclose(file); }
78
79 void toggle_window (struct WinMeta * win_meta, struct Win * win) {
80 // Toggle display of window win.
81   if (0 != win->curses)
82     suspend_window(win_meta, win);
83   else
84     append_window(win_meta, win); }
85
86 void growshrink_active_window (struct WinMeta * win_meta, char change) {
87 // Grow or shrink active window horizontally or vertically by one cell size.
88   if (0 != win_meta->active) {
89     uint16_t height = win_meta->active->height;
90     uint16_t width = win_meta->active->width;
91     if      (change == '-')
92       height--;
93     else if (change == '+')
94       height++;
95     else if (change == '_')
96       width--;
97     else if (change == '*')
98       width++;
99     resize_active_window (win_meta, height, width); } }
100
101 struct Map init_map () {
102 // Initialize map with some experimental start values.
103   struct Map map;
104   map.width = 64;
105   map.height = 64;
106   map.offset_x = 0;
107   map.offset_y = 0;
108   map.cells = malloc(map.width * map.height);
109   uint16_t x, y, ran;
110   char terrain;
111   for (y = 0; y < map.height; y++)
112     for (x = 0; x < map.width; x++) {
113       terrain = '.';
114       ran = rrand(0, 0);
115       if (   0 == ran % ((x*x) / 3 + 1)
116           || 0 == ran % ((y*y) / 3 + 1)
117           || 0 == ran % ((map.width - x - 1) * (map.width - x - 1) / 3 + 1)
118           || 0 == ran %((map.height - y - 1) * (map.height - y - 1) / 3 + 1))
119         terrain = ' ';
120       map.cells[(y * map.width) + x] = terrain; }
121   return map; }
122
123 void map_scroll (struct Map * map, char dir) {
124 // Scroll map into direction dir if possible by changing the offset.
125   if      ('n' == dir && map->offset_y > 0)
126     map->offset_y--;
127   else if ('s' == dir)
128     map->offset_y++;
129   else if ('w' == dir && map->offset_x > 0)
130     map->offset_x--;
131   else if ('e' == dir)
132     map->offset_x++; }
133
134 void next_turn (struct World * world) {
135 // Increment turn and move enemy.
136   world->turn++;
137   rrand(1, world->seed * world->turn);
138   char d = rrand(0, 0) % 5;
139   uint16_t ty = world->monster->y;
140   uint16_t tx = world->monster->x;
141   if (1 == d)
142     ty++;
143   else if (2 == d)
144     ty--;
145   else if (3 == d)
146     tx++;
147   else if (4 == d)
148     tx--;
149   if (tx == world->player->x && ty == world->player->y)
150     update_log(world, "\nThe monster hits you.");
151   else if (is_passable(world, tx, ty)) {
152     world->monster->y = ty;
153     world->monster->x = tx; } }
154
155 void update_log (struct World * world, char * text) {
156 // Update log with new text to be appended.
157   char * new_text;
158   uint16_t len_old = strlen(world->log);
159   uint16_t len_new = strlen(text);
160   uint16_t len_whole = len_old + len_new + 1;
161   new_text = calloc(len_whole, sizeof(char));
162   memcpy(new_text, world->log, len_old);
163   memcpy(new_text + len_old, text, len_new);
164   free(world->log);
165   world->log = new_text; }
166
167 char is_passable (struct World * world, uint16_t x, uint16_t y) {
168 // Check if coordinate on (or beyond) map is accessible to movement.
169   char passable = 0;
170   if (0 <= x && x < world->map->width && 0 <= y && y < world->map->height)
171     if ('.' == world->map->cells[y * world->map->width + x])
172       passable = 1;
173   return passable; }
174
175 void record_action (char action) {
176 //
177   FILE * file = fopen("record", "a");
178   fputc(action, file);
179   fclose(file); }
180
181 void move_player (struct World * world, char d) {
182 // Move player in direction d, increment turn counter and update log.
183   static char prev = 0;
184   char success = 0;
185   char * dir;
186   uint16_t ty = world->player->y;
187   uint16_t tx = world->player->x;
188   if ('s' == d) {
189     dir = "south";
190     ty++; }
191   if ('n' == d) {
192     dir = "north";
193     ty--; }
194   if ('w' == d) {
195     dir = "west";
196     tx--; }
197   if ('e' == d) {
198     dir = "east";
199     tx++; }
200   if (ty == world->monster->y && tx == world->monster->x)
201     success = 2;
202   else if (is_passable(world, tx, ty)) {
203     success = 1;
204     world->player->y = ty;
205     world->player->x = tx; }
206   if (success * d == prev)
207     update_log (world, ".");
208   else {
209     if (2 == success)
210       update_log (world, "\nYou hit the monster.");
211     else {
212       char * msg = calloc(25, sizeof(char));
213       char * msg_content = "You fail to move";
214       if (1 == success)
215         msg_content = "You move";
216       sprintf(msg, "\n%s %s.", msg_content, dir);
217       update_log (world, msg);
218       free(msg); } }
219   prev = success * d;
220   if (1 == world->interactive)
221     record_action(d);
222   next_turn (world); }
223
224 void player_wait (struct World * world) {
225 // Make player wait one turn.
226   if (1 == world->interactive)
227     record_action(0);
228   next_turn (world);
229   update_log (world, "\nYou wait."); }
230
231 void startpos(struct World * world) {
232 // Initialize some default starting values.
233   world->turn = 1;
234   world->player->y = 8;
235   world->player->x = 8;
236   world->monster->y = 55;
237   world->monster->x = 55; }
238
239 unsigned char meta_keys(int key, struct World * world, struct WinMeta * win_meta, struct Win * win_keys,
240                         struct Win * win_map, struct Win * win_info, struct Win * win_log) {
241 // Call some meta program / window management actions dependent on key. Return 1 to signal quitting.
242   if (key == get_action_key(world->keybindings, "quit"))
243     return 1;
244   if (key == get_action_key(world->keybindings, "scroll pad right"))
245     scroll_pad (win_meta, '+');
246   else if (key == get_action_key(world->keybindings, "scroll pad left"))
247     scroll_pad (win_meta, '-');
248   else if (key == get_action_key(world->keybindings, "toggle keys window"))
249     toggle_window(win_meta, win_keys);
250   else if (key == get_action_key(world->keybindings, "toggle map window"))
251     toggle_window(win_meta, win_map);
252   else if (key == get_action_key(world->keybindings, "toggle info window"))
253     toggle_window(win_meta, win_info);
254   else if (key == get_action_key(world->keybindings, "toggle log window"))
255     toggle_window(win_meta, win_log);
256   else if (key == get_action_key(world->keybindings, "cycle forwards"))
257     cycle_active_window(win_meta, 'n');
258   else if (key == get_action_key(world->keybindings, "cycle backwards"))
259     cycle_active_window(win_meta, 'p');
260   else if (key == get_action_key(world->keybindings, "shift forwards"))
261     shift_active_window(win_meta, 'f');
262   else if (key == get_action_key(world->keybindings, "shift backwards"))
263     shift_active_window(win_meta, 'b');
264   else if (key == get_action_key(world->keybindings, "grow horizontally"))
265     growshrink_active_window(win_meta, '*');
266   else if (key == get_action_key(world->keybindings, "shrink horizontally"))
267     growshrink_active_window(win_meta, '_');
268   else if (key == get_action_key(world->keybindings, "grow vertically"))
269     growshrink_active_window(win_meta, '+');
270   else if (key == get_action_key(world->keybindings, "shrink vertically"))
271     growshrink_active_window(win_meta, '-');
272   else if (key == get_action_key(world->keybindings, "save keys"))
273     save_keybindings(world);
274   else if (key == get_action_key(world->keybindings, "keys nav up"))
275     keyswin_move_selection (world, 'u');
276   else if (key == get_action_key(world->keybindings, "keys nav down"))
277     keyswin_move_selection (world, 'd');
278   else if (key == get_action_key(world->keybindings, "keys mod"))
279     keyswin_mod_key (world, win_meta);
280   else if (key == get_action_key(world->keybindings, "map up"))
281     map_scroll (world->map, 'n');
282   else if (key == get_action_key(world->keybindings, "map down"))
283     map_scroll (world->map, 's');
284   else if (key == get_action_key(world->keybindings, "map right"))
285     map_scroll (world->map, 'e');
286   else if (key == get_action_key(world->keybindings, "map left"))
287     map_scroll (world->map, 'w');
288   return 0; }
289
290 int main (int argc, char *argv[]) {
291   struct World world;
292   world.interactive = 1;
293   int opt;
294   while ((opt = getopt(argc, argv, "s")) != -1) {
295     switch (opt) {
296       case 's':
297         world.interactive = 0;
298         break;
299       default:
300         exit(EXIT_FAILURE); } }
301
302   init_keybindings(&world);
303   world.log = calloc(1, sizeof(char));
304   update_log (&world, " ");
305   struct Player player;
306   world.player = &player;
307   struct Monster monster;
308   world.monster = &monster;
309   FILE * file;
310
311   if (0 == world.interactive) {
312     startpos(&world);
313     file = fopen("record", "r");
314     world.seed = read_uint32_bigendian(file); }
315   else {
316     if (0 == access("savefile", F_OK))
317       load_game(&world);
318     else {
319       startpos(&world);
320       world.seed = time(NULL);
321       file = fopen("record", "w");
322       write_uint32_bigendian(world.seed, file);
323       fclose(file); } }
324   rrand(1, world.seed);
325   struct Map map = init_map();
326   world.map = &map;
327
328   WINDOW * screen = initscr();
329   noecho();
330   curs_set(0);
331   keypad(screen, TRUE);
332   raw();
333   struct WinMeta win_meta = init_win_meta(screen);
334   struct Win win_keys = init_window(&win_meta, "Keys", &world, draw_keys_win);
335   struct Win win_map = init_window(&win_meta, "Map", &world, draw_map_win);
336   struct Win win_info = init_window(&win_meta, "Info", &world, draw_info_win);
337   struct Win win_log = init_window(&win_meta, "Log", &world, draw_log_win);
338   win_keys.width = 29;
339   win_map.width = win_meta.width - win_keys.width - win_log.width - 2;
340   win_info.height = 1;
341   win_log.height = win_meta.height - 3;
342   toggle_window(&win_meta, &win_keys);
343   toggle_window(&win_meta, &win_map);
344   toggle_window(&win_meta, &win_info);
345   toggle_window(&win_meta, &win_log);
346
347   int key;
348   unsigned char result;
349   if (0 == world.interactive) {
350     int action;
351     while (1) {
352       draw_all_windows (&win_meta);
353       key = getch();
354       if (key == get_action_key(world.keybindings, "wait / next turn") ) {
355         action = getc(file);
356         if (EOF == action)
357           break;
358         else if (0 == action)
359           player_wait (&world);
360         else if ('s' == action)
361           move_player(&world, 's');
362         else if ('n' == action)
363           move_player(&world, 'n');
364         else if ('e' == action)
365           move_player(&world, 'e');
366         else if ('w' == action)
367           move_player(&world, 'w'); }
368       else
369         result = meta_keys(key, &world, &win_meta, &win_keys, &win_map, &win_info, &win_log);
370         if (1 == result)
371           break; } }
372   else {
373     uint32_t last_turn = 0;
374     while (1) {
375       if (last_turn != world.turn) {
376         save_game(&world);
377         last_turn = world.turn; }
378       draw_all_windows (&win_meta);
379       key = getch();
380       if      (key == get_action_key(world.keybindings, "player down"))
381         move_player(&world, 's');
382       else if (key == get_action_key(world.keybindings, "player up"))
383         move_player(&world, 'n');
384       else if (key == get_action_key(world.keybindings, "player right"))
385         move_player(&world, 'e');
386       else if (key == get_action_key(world.keybindings, "player left"))
387         move_player(&world, 'w');
388       else if (key == get_action_key(world.keybindings, "wait / next turn"))
389         player_wait (&world);
390       else
391         result = meta_keys(key, &world, &win_meta, &win_keys, &win_map, &win_info, &win_log);
392         if (1 == result)
393           break; } }
394
395   free(map.cells);
396   for (key = 0; key <= world.keyswindata->max; key++)
397     free(world.keybindings[key].name);
398   free(world.keybindings);
399   free(world.keyswindata);
400   free(world.log);
401
402   endwin();
403   return 0; }