home · contact · privacy
Working towards roguelike permadeath conventions: Saving happens automatically now...
[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_seed(struct World * world) {
58 // Load seed integer from seed 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_seed(struct World * world) {
69 // Save seed integer to seed 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 move_player (struct World * world, char d) {
176 // Move player in direction d, increment turn counter and update log.
177   static char prev = 0;
178   char success = 0;
179   char * dir;
180   uint16_t ty = world->player->y;
181   uint16_t tx = world->player->x;
182   if ('s' == d) {
183     dir = "south";
184     ty++; }
185   if ('n' == d) {
186     dir = "north";
187     ty--; }
188   if ('w' == d) {
189     dir = "west";
190     tx--; }
191   if ('e' == d) {
192     dir = "east";
193     tx++; }
194   if (ty == world->monster->y && tx == world->monster->x)
195     success = 2;
196   else if (is_passable(world, tx, ty)) {
197     success = 1;
198     world->player->y = ty;
199     world->player->x = tx; }
200   if (success * d == prev)
201     update_log (world, ".");
202   else {
203     if (2 == success)
204       update_log (world, "\nYou hit the monster.");
205     else {
206       char * msg = calloc(25, sizeof(char));
207       char * msg_content = "You fail to move";
208       if (1 == success)
209         msg_content = "You move";
210       sprintf(msg, "\n%s %s.", msg_content, dir);
211       update_log (world, msg);
212       free(msg); } }
213   prev = success * d;
214   next_turn (world); }
215
216 void player_wait (struct World * world) {
217 // Make player wait one turn.
218   next_turn (world);
219   update_log (world, "\nYou wait."); }
220
221 int main (int argc, char *argv[]) {
222   struct World world;
223   struct Player player;
224   world.player = &player;
225   struct Monster monster;
226   world.monster = &monster;
227   if (0 == access("savefile", F_OK))
228     load_seed(&world);
229   else {
230     player.y = 8;
231     player.x = 8;
232     monster.y = 55;
233     monster.x = 55;
234     world.seed = time(NULL);
235     world.turn = 1; }
236   rrand(1, world.seed);
237
238   init_keybindings(&world);
239   world.log = calloc(1, sizeof(char));
240   update_log (&world, "Start!");
241   struct Map map = init_map();
242   world.map = &map;
243
244   WINDOW * screen = initscr();
245   noecho();
246   curs_set(0);
247   keypad(screen, TRUE);
248   raw();
249   struct WinMeta win_meta = init_win_meta(screen);
250   struct Win win_keys = init_window(&win_meta, "Keys", &world, draw_keys_win);
251   struct Win win_map = init_window(&win_meta, "Map", &world, draw_map_win);
252   struct Win win_info = init_window(&win_meta, "Info", &world, draw_info_win);
253   struct Win win_log = init_window(&win_meta, "Log", &world, draw_log_win);
254
255   int key;
256   uint32_t last_turn = 0;
257   while (1) {
258     if (last_turn != world.turn) {
259       save_seed(&world);
260       last_turn = world.turn; }
261     draw_all_windows (&win_meta);
262     key = getch();
263     if      (key == get_action_key(world.keybindings, "quit"))
264       break;
265     else if (key == get_action_key(world.keybindings, "scroll pad right"))
266       scroll_pad (&win_meta, '+');
267     else if (key == get_action_key(world.keybindings, "scroll pad left"))
268       scroll_pad (&win_meta, '-');
269     else if (key == get_action_key(world.keybindings, "toggle keys window"))
270       toggle_window(&win_meta, &win_keys);
271     else if (key == get_action_key(world.keybindings, "toggle map window"))
272       toggle_window(&win_meta, &win_map);
273     else if (key == get_action_key(world.keybindings, "toggle info window"))
274       toggle_window(&win_meta, &win_info);
275     else if (key == get_action_key(world.keybindings, "toggle log window"))
276       toggle_window(&win_meta, &win_log);
277     else if (key == get_action_key(world.keybindings, "cycle forwards"))
278       cycle_active_window(&win_meta, 'n');
279     else if (key == get_action_key(world.keybindings, "cycle backwards"))
280       cycle_active_window(&win_meta, 'p');
281     else if (key == get_action_key(world.keybindings, "shift forwards"))
282       shift_active_window(&win_meta, 'f');
283     else if (key == get_action_key(world.keybindings, "shift backwards"))
284       shift_active_window(&win_meta, 'b');
285     else if (key == get_action_key(world.keybindings, "grow horizontally"))
286       growshrink_active_window(&win_meta, '*');
287     else if (key == get_action_key(world.keybindings, "shrink horizontally"))
288       growshrink_active_window(&win_meta, '_');
289     else if (key == get_action_key(world.keybindings, "grow vertically"))
290       growshrink_active_window(&win_meta, '+');
291     else if (key == get_action_key(world.keybindings, "shrink vertically"))
292       growshrink_active_window(&win_meta, '-');
293     else if (key == get_action_key(world.keybindings, "save keys"))
294       save_keybindings(&world);
295     else if (key == get_action_key(world.keybindings, "keys nav up"))
296       keyswin_move_selection (&world, 'u');
297     else if (key == get_action_key(world.keybindings, "keys nav down"))
298       keyswin_move_selection (&world, 'd');
299     else if (key == get_action_key(world.keybindings, "keys mod"))
300       keyswin_mod_key (&world, &win_meta);
301     else if (key == get_action_key(world.keybindings, "map up"))
302       map_scroll (&map, 'n');
303     else if (key == get_action_key(world.keybindings, "map down"))
304       map_scroll (&map, 's');
305     else if (key == get_action_key(world.keybindings, "map right"))
306       map_scroll (&map, 'e');
307     else if (key == get_action_key(world.keybindings, "map left"))
308       map_scroll (&map, 'w');
309     else if (key == get_action_key(world.keybindings, "player down"))
310       move_player(&world, 's');
311     else if (key == get_action_key(world.keybindings, "player up"))
312       move_player(&world, 'n');
313     else if (key == get_action_key(world.keybindings, "player right"))
314       move_player(&world, 'e');
315     else if (key == get_action_key(world.keybindings, "player left"))
316       move_player(&world, 'w');
317     else if (key == get_action_key(world.keybindings, "wait") )
318       player_wait (&world); }
319
320   free(map.cells);
321   for (key = 0; key <= world.keyswindata->max; key++)
322     free(world.keybindings[key].name);
323   free(world.keybindings);
324   free(world.keyswindata);
325   free(world.log);
326
327   endwin();
328   return 0; }