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