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