home · contact · privacy
Corrected parentheses.
[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   uint32_t size = map.width * map.height;
109   map.cells = malloc(size);
110   uint16_t y, x;
111   for (y = 0; y < map.height; y++)
112     for (x = 0; x < map.width; x++)
113       map.cells[(y * map.width) + x] = '~';
114   map.cells[size / 2 + (map.width / 2)] = '.';
115   uint32_t repeats, root, curpos;
116   for (root = 0; root * root * root < size; root++);
117   for (repeats = 0; repeats < size * root; repeats++) {
118     y = rrand(0, 0) % map.height;
119     x = rrand(0, 0) % map.width;
120     curpos = y * map.width + x;
121     if ('~' == map.cells[curpos] &&
122         (   (curpos >= map.width && '.' == map.cells[curpos - map.width])
123          || (curpos < map.width * (map.height - 1) && '.' == map.cells[curpos + map.width])
124          || (curpos > 0 && '.' == map.cells[curpos - 1] && curpos % map.width != 0)
125          || (curpos < (map.width * map.height) && '.' == map.cells[curpos + 1] && (curpos + 1) % map.width != 0)))
126       map.cells[y * map.width + x] = '.'; }
127   return map; }
128
129 void map_scroll (struct Map * map, char dir) {
130 // Scroll map into direction dir if possible by changing the offset.
131   if      ('n' == dir && map->offset_y > 0)
132     map->offset_y--;
133   else if ('s' == dir)
134     map->offset_y++;
135   else if ('w' == dir && map->offset_x > 0)
136     map->offset_x--;
137   else if ('e' == dir)
138     map->offset_x++; }
139
140 void next_turn (struct World * world) {
141 // Increment turn and move enemy.
142   world->turn++;
143   rrand(1, world->seed * world->turn);
144   char d = rrand(0, 0) % 5;
145   uint16_t ty = world->monster->y;
146   uint16_t tx = world->monster->x;
147   if (1 == d)
148     ty++;
149   else if (2 == d)
150     ty--;
151   else if (3 == d)
152     tx++;
153   else if (4 == d)
154     tx--;
155   if (tx == world->player->x && ty == world->player->y)
156     update_log(world, "\nThe monster hits you.");
157   else if (is_passable(world, tx, ty)) {
158     world->monster->y = ty;
159     world->monster->x = tx; } }
160
161 void update_log (struct World * world, char * text) {
162 // Update log with new text to be appended.
163   char * new_text;
164   uint16_t len_old = strlen(world->log);
165   uint16_t len_new = strlen(text);
166   uint16_t len_whole = len_old + len_new + 1;
167   new_text = calloc(len_whole, sizeof(char));
168   memcpy(new_text, world->log, len_old);
169   memcpy(new_text + len_old, text, len_new);
170   free(world->log);
171   world->log = new_text; }
172
173 char is_passable (struct World * world, uint16_t x, uint16_t y) {
174 // Check if coordinate on (or beyond) map is accessible to movement.
175   char passable = 0;
176   if (0 <= x && x < world->map->width && 0 <= y && y < world->map->height)
177     if ('.' == world->map->cells[y * world->map->width + x])
178       passable = 1;
179   return passable; }
180
181 void record_action (char action) {
182 // Append action to game record file.
183   FILE * file = fopen("record", "a");
184   fputc(action, file);
185   fclose(file); }
186
187 void move_player (struct World * world, char d) {
188 // Move player in direction d, increment turn counter and update log.
189   static char prev = 0;
190   char success = 0;
191   char * dir;
192   uint16_t ty = world->player->y;
193   uint16_t tx = world->player->x;
194   if ('s' == d) {
195     dir = "south";
196     ty++; }
197   if ('n' == d) {
198     dir = "north";
199     ty--; }
200   if ('w' == d) {
201     dir = "west";
202     tx--; }
203   if ('e' == d) {
204     dir = "east";
205     tx++; }
206   if (ty == world->monster->y && tx == world->monster->x)
207     success = 2;
208   else if (is_passable(world, tx, ty)) {
209     success = 1;
210     world->player->y = ty;
211     world->player->x = tx; }
212   if (success * d == prev)
213     update_log (world, ".");
214   else {
215     if (2 == success)
216       update_log (world, "\nYou hit the monster.");
217     else {
218       char * msg = calloc(25, sizeof(char));
219       char * msg_content = "You fail to move";
220       if (1 == success)
221         msg_content = "You move";
222       sprintf(msg, "\n%s %s.", msg_content, dir);
223       update_log (world, msg);
224       free(msg); } }
225   prev = success * d;
226   if (1 == world->interactive)
227     record_action(d);
228   next_turn (world); }
229
230 void player_wait (struct World * world) {
231 // Make player wait one turn.
232   if (1 == world->interactive)
233     record_action(0);
234   next_turn (world);
235   update_log (world, "\nYou wait."); }
236
237 void startpos(struct World * world) {
238 // Initialize some default starting values.
239   world->turn = 1;
240   world->player->y = 8;
241   world->player->x = 8;
242   world->monster->y = 55;
243   world->monster->x = 55; }
244
245 unsigned char meta_keys(int key, struct World * world, struct WinMeta * win_meta, struct Win * win_keys,
246                         struct Win * win_map, struct Win * win_info, struct Win * win_log) {
247 // Call some meta program / window management actions dependent on key. Return 1 to signal quitting.
248   if (key == get_action_key(world->keybindings, "quit"))
249     return 1;
250   else if (key == get_action_key(world->keybindings, "scroll pad right"))
251     scroll_pad (win_meta, '+');
252   else if (key == get_action_key(world->keybindings, "scroll pad left"))
253     scroll_pad (win_meta, '-');
254   else if (key == get_action_key(world->keybindings, "toggle keys window"))
255     toggle_window(win_meta, win_keys);
256   else if (key == get_action_key(world->keybindings, "toggle map window"))
257     toggle_window(win_meta, win_map);
258   else if (key == get_action_key(world->keybindings, "toggle info window"))
259     toggle_window(win_meta, win_info);
260   else if (key == get_action_key(world->keybindings, "toggle log window"))
261     toggle_window(win_meta, win_log);
262   else if (key == get_action_key(world->keybindings, "cycle forwards"))
263     cycle_active_window(win_meta, 'n');
264   else if (key == get_action_key(world->keybindings, "cycle backwards"))
265     cycle_active_window(win_meta, 'p');
266   else if (key == get_action_key(world->keybindings, "shift forwards"))
267     shift_active_window(win_meta, 'f');
268   else if (key == get_action_key(world->keybindings, "shift backwards"))
269     shift_active_window(win_meta, 'b');
270   else if (key == get_action_key(world->keybindings, "grow horizontally"))
271     growshrink_active_window(win_meta, '*');
272   else if (key == get_action_key(world->keybindings, "shrink horizontally"))
273     growshrink_active_window(win_meta, '_');
274   else if (key == get_action_key(world->keybindings, "grow vertically"))
275     growshrink_active_window(win_meta, '+');
276   else if (key == get_action_key(world->keybindings, "shrink vertically"))
277     growshrink_active_window(win_meta, '-');
278   else if (key == get_action_key(world->keybindings, "save keys"))
279     save_keybindings(world);
280   else if (key == get_action_key(world->keybindings, "keys nav up"))
281     keyswin_move_selection (world, 'u');
282   else if (key == get_action_key(world->keybindings, "keys nav down"))
283     keyswin_move_selection (world, 'd');
284   else if (key == get_action_key(world->keybindings, "keys mod"))
285     keyswin_mod_key (world, win_meta);
286   else if (key == get_action_key(world->keybindings, "map up"))
287     map_scroll (world->map, 'n');
288   else if (key == get_action_key(world->keybindings, "map down"))
289     map_scroll (world->map, 's');
290   else if (key == get_action_key(world->keybindings, "map right"))
291     map_scroll (world->map, 'e');
292   else if (key == get_action_key(world->keybindings, "map left"))
293     map_scroll (world->map, 'w');
294   return 0; }
295
296 int main (int argc, char *argv[]) {
297   struct World world;
298   world.interactive = 1;
299   int opt;
300   uint32_t start_turn;
301   while ((opt = getopt(argc, argv, "s::")) != -1) {
302     switch (opt) {
303       case 's':
304         world.interactive = 0;
305         start_turn = 0;
306         if (optarg)
307           start_turn = atoi(optarg);
308         break;
309       default:
310         exit(EXIT_FAILURE); } }
311
312   init_keybindings(&world);
313   world.log = calloc(1, sizeof(char));
314   update_log (&world, " ");
315   struct Player player;
316   world.player = &player;
317   struct Monster monster;
318   world.monster = &monster;
319   FILE * file;
320
321   if (0 == world.interactive) {
322     startpos(&world);
323     file = fopen("record", "r");
324     world.seed = read_uint32_bigendian(file); }
325   else {
326     if (0 == access("savefile", F_OK))
327       load_game(&world);
328     else {
329       startpos(&world);
330       world.seed = time(NULL);
331       file = fopen("record", "w");
332       write_uint32_bigendian(world.seed, file);
333       fclose(file); } }
334   rrand(1, world.seed);
335   struct Map map = init_map();
336   world.map = &map;
337
338   WINDOW * screen = initscr();
339   noecho();
340   curs_set(0);
341   keypad(screen, TRUE);
342   raw();
343   struct WinMeta win_meta = init_win_meta(screen);
344   struct Win win_keys = init_window(&win_meta, "Keys", &world, draw_keys_win);
345   struct Win win_map = init_window(&win_meta, "Map", &world, draw_map_win);
346   struct Win win_info = init_window(&win_meta, "Info", &world, draw_info_win);
347   struct Win win_log = init_window(&win_meta, "Log", &world, draw_log_win);
348   win_keys.width = 29;
349   win_map.width = win_meta.width - win_keys.width - win_log.width - 2;
350   win_info.height = 1;
351   win_log.height = win_meta.height - 3;
352   toggle_window(&win_meta, &win_keys);
353   toggle_window(&win_meta, &win_map);
354   toggle_window(&win_meta, &win_info);
355   toggle_window(&win_meta, &win_log);
356
357   int key;
358   unsigned char quit_called;
359   if (0 == world.interactive) {
360     unsigned char still_reading_file = 1;
361     int action;
362     while (1) {
363       if (start_turn == world.turn)
364         start_turn = 0;
365       if (0 == start_turn) {
366         draw_all_windows (&win_meta);
367         key = getch(); }
368       if (1 == still_reading_file &&
369           (world.turn < start_turn || key == get_action_key(world.keybindings, "wait / next turn")) ) {
370         action = getc(file);
371         if (EOF == action) {
372           start_turn = 0;
373           still_reading_file = 0; }
374         else if (0 == action)
375           player_wait (&world);
376         else if ('s' == action)
377           move_player(&world, 's');
378         else if ('n' == action)
379           move_player(&world, 'n');
380         else if ('e' == action)
381           move_player(&world, 'e');
382         else if ('w' == action)
383           move_player(&world, 'w'); }
384       else
385         quit_called = meta_keys(key, &world, &win_meta, &win_keys, &win_map, &win_info, &win_log);
386         if (1 == quit_called)
387           break; } }
388   else {
389     uint32_t last_turn = 0;
390     while (1) {
391       if (last_turn != world.turn) {
392         save_game(&world);
393         last_turn = world.turn; }
394       draw_all_windows (&win_meta);
395       key = getch();
396       if      (key == get_action_key(world.keybindings, "player down"))
397         move_player(&world, 's');
398       else if (key == get_action_key(world.keybindings, "player up"))
399         move_player(&world, 'n');
400       else if (key == get_action_key(world.keybindings, "player right"))
401         move_player(&world, 'e');
402       else if (key == get_action_key(world.keybindings, "player left"))
403         move_player(&world, 'w');
404       else if (key == get_action_key(world.keybindings, "wait / next turn"))
405         player_wait (&world);
406       else
407         quit_called = meta_keys(key, &world, &win_meta, &win_keys, &win_map, &win_info, &win_log);
408         if (1 == quit_called)
409           break; } }
410
411   free(map.cells);
412   for (key = 0; key <= world.keyswindata->max; key++)
413     free(world.keybindings[key].name);
414   free(world.keybindings);
415   free(world.keyswindata);
416   free(world.log);
417
418   endwin();
419   return 0; }