home · contact · privacy
Replaced seed file system with a game save file system.
[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   char d = rrand(0, 0) % 5;
138   uint16_t ty = world->monster->y;
139   uint16_t tx = world->monster->x;
140   if (1 == d)
141     ty++;
142   else if (2 == d)
143     ty--;
144   else if (3 == d)
145     tx++;
146   else if (4 == d)
147     tx--;
148   if (tx == world->player->x && ty == world->player->y)
149     update_log(world, "\nThe monster hits you.");
150   else if (is_passable(world, tx, ty)) {
151     world->monster->y = ty;
152     world->monster->x = tx; } }
153
154 void update_log (struct World * world, char * text) {
155 // Update log with new text to be appended.
156   char * new_text;
157   uint16_t len_old = strlen(world->log);
158   uint16_t len_new = strlen(text);
159   uint16_t len_whole = len_old + len_new + 1;
160   new_text = calloc(len_whole, sizeof(char));
161   memcpy(new_text, world->log, len_old);
162   memcpy(new_text + len_old, text, len_new);
163   free(world->log);
164   world->log = new_text; }
165
166 char is_passable (struct World * world, uint16_t x, uint16_t y) {
167 // Check if coordinate on (or beyond) map is accessible to movement.
168   char passable = 0;
169   if (0 <= x && x < world->map->width && 0 <= y && y < world->map->height)
170     if ('.' == world->map->cells[y * world->map->width + x])
171       passable = 1;
172   return passable; }
173
174 void move_player (struct World * world, char d) {
175 // Move player in direction d, increment turn counter and update log.
176   static char prev = 0;
177   char success = 0;
178   char * dir;
179   uint16_t ty = world->player->y;
180   uint16_t tx = world->player->x;
181   if ('s' == d) {
182     dir = "south";
183     ty++; }
184   if ('n' == d) {
185     dir = "north";
186     ty--; }
187   if ('w' == d) {
188     dir = "west";
189     tx--; }
190   if ('e' == d) {
191     dir = "east";
192     tx++; }
193   if (ty == world->monster->y && tx == world->monster->x)
194     success = 2;
195   else if (is_passable(world, tx, ty)) {
196     success = 1;
197     world->player->y = ty;
198     world->player->x = tx; }
199   if (success * d == prev)
200     update_log (world, ".");
201   else {
202     if (2 == success)
203       update_log (world, "\nYou hit the monster.");
204     else {
205       char * msg = calloc(25, sizeof(char));
206       char * msg_content = "You fail to move";
207       if (1 == success)
208         msg_content = "You move";
209       sprintf(msg, "\n%s %s.", msg_content, dir);
210       update_log (world, msg);
211       free(msg); } }
212   prev = success * d;
213   next_turn (world); }
214
215 void player_wait (struct World * world) {
216 // Make player wait one turn.
217   next_turn (world);
218   update_log (world, "\nYou wait."); }
219
220 int main (int argc, char *argv[]) {
221   struct World world;
222   struct Player player;
223   world.player = &player;
224   struct Monster monster;
225   world.monster = &monster;
226   if (0 == access("savefile", F_OK))
227     load_seed(&world);
228   else {
229     player.y = 8;
230     player.x = 8;
231     monster.y = 55;
232     monster.x = 55;
233     world.seed = time(NULL);
234     world.turn = 0; }
235   rrand(1, world.seed);
236
237   init_keybindings(&world);
238   world.log = calloc(1, sizeof(char));
239   update_log (&world, "Start!");
240   struct Map map = init_map();
241   world.map = &map;
242
243   WINDOW * screen = initscr();
244   noecho();
245   curs_set(0);
246   keypad(screen, TRUE);
247   raw();
248   struct WinMeta win_meta = init_win_meta(screen);
249   struct Win win_keys = init_window(&win_meta, "Keys", &world, draw_keys_win);
250   struct Win win_map = init_window(&win_meta, "Map", &world, draw_map_win);
251   struct Win win_info = init_window(&win_meta, "Info", &world, draw_info_win);
252   struct Win win_log = init_window(&win_meta, "Log", &world, draw_log_win);
253
254   int key;
255   while (1) {
256     draw_all_windows (&win_meta);
257     key = getch();
258     if      (key == get_action_key(world.keybindings, "quit"))
259       break;
260     else if (key == get_action_key(world.keybindings, "scroll pad right"))
261       scroll_pad (&win_meta, '+');
262     else if (key == get_action_key(world.keybindings, "scroll pad left"))
263       scroll_pad (&win_meta, '-');
264     else if (key == get_action_key(world.keybindings, "toggle keys window"))
265       toggle_window(&win_meta, &win_keys);
266     else if (key == get_action_key(world.keybindings, "toggle map window"))
267       toggle_window(&win_meta, &win_map);
268     else if (key == get_action_key(world.keybindings, "toggle info window"))
269       toggle_window(&win_meta, &win_info);
270     else if (key == get_action_key(world.keybindings, "toggle log window"))
271       toggle_window(&win_meta, &win_log);
272     else if (key == get_action_key(world.keybindings, "cycle forwards"))
273       cycle_active_window(&win_meta, 'n');
274     else if (key == get_action_key(world.keybindings, "cycle backwards"))
275       cycle_active_window(&win_meta, 'p');
276     else if (key == get_action_key(world.keybindings, "shift forwards"))
277       shift_active_window(&win_meta, 'f');
278     else if (key == get_action_key(world.keybindings, "shift backwards"))
279       shift_active_window(&win_meta, 'b');
280     else if (key == get_action_key(world.keybindings, "grow horizontally"))
281       growshrink_active_window(&win_meta, '*');
282     else if (key == get_action_key(world.keybindings, "shrink horizontally"))
283       growshrink_active_window(&win_meta, '_');
284     else if (key == get_action_key(world.keybindings, "grow vertically"))
285       growshrink_active_window(&win_meta, '+');
286     else if (key == get_action_key(world.keybindings, "shrink vertically"))
287       growshrink_active_window(&win_meta, '-');
288     else if (key == get_action_key(world.keybindings, "save keys"))
289       save_keybindings(&world);
290     else if (key == get_action_key(world.keybindings, "keys nav up"))
291       keyswin_move_selection (&world, 'u');
292     else if (key == get_action_key(world.keybindings, "keys nav down"))
293       keyswin_move_selection (&world, 'd');
294     else if (key == get_action_key(world.keybindings, "keys mod"))
295       keyswin_mod_key (&world, &win_meta);
296     else if (key == get_action_key(world.keybindings, "save game"))
297       save_seed(&world);
298     else if (key == get_action_key(world.keybindings, "map up"))
299       map_scroll (&map, 'n');
300     else if (key == get_action_key(world.keybindings, "map down"))
301       map_scroll (&map, 's');
302     else if (key == get_action_key(world.keybindings, "map right"))
303       map_scroll (&map, 'e');
304     else if (key == get_action_key(world.keybindings, "map left"))
305       map_scroll (&map, 'w');
306     else if (key == get_action_key(world.keybindings, "player down"))
307       move_player(&world, 's');
308     else if (key == get_action_key(world.keybindings, "player up"))
309       move_player(&world, 'n');
310     else if (key == get_action_key(world.keybindings, "player right"))
311       move_player(&world, 'e');
312     else if (key == get_action_key(world.keybindings, "player left"))
313       move_player(&world, 'w');
314     else if (key == get_action_key(world.keybindings, "wait") )
315       player_wait (&world); }
316
317   free(map.cells);
318   for (key = 0; key <= world.keyswindata->max; key++)
319     free(world.keybindings[key].name);
320   free(world.keybindings);
321   free(world.keyswindata);
322   free(world.log);
323
324   endwin();
325   return 0; }