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