home · contact · privacy
Moved meta_keys() into new library "control" to soon include all key-press processing.
[plomrogue] / src / main.c
1 /* main.c */
2
3 #include "main.h"
4 #include <stdlib.h> /* for atoi(), exit(), EXIT_FAILURE, calloc() */
5 #include <stdio.h> /* for FILE typedef, F_OK, rename() */
6 #include <ncurses.h> /* for initscr(), noecho(), curs_set(), keypad(), raw() */
7 #include <time.h> /* for time() */
8 #include <unistd.h> /* for unlink(), getopt(), optarg */
9 #include <stdint.h> /* for uint8_t */
10 #include <errno.h> /* for errno */
11 #include "windows.h" /* for structs WinMeta, Win, init_win(), init_win_meta(),
12                       * draw_all_wins()
13                       */
14 #include "draw_wins.h" /* for draw_keys_win(), draw_map_win(), draw_info_win(),
15                         * draw_log_win()
16                         */
17 #include "keybindings.h" /* for initkeybindings(), get_action_key() */
18 #include "readwrite.h" /* for [read/write]_uint[8/16/32][_bigendian]() */
19 #include "map_objects.h" /* for structs Monster, Item, Player,
20                           * init_map_object_defs(), read_map_objects(),
21                           * build_map_objects()
22                           */
23 #include "map_object_actions.h" /* for player_wait(), move_player() */
24 #include "map.h" /* for struct Map, init_map() */
25 #include "misc.h" /* for update_log(), toggle_window(), find_passable_pos(),
26                    * meta_keys(), save_game()
27                    */
28 #include "yx_uint16.h" /* for dir enum */
29 #include "rrand.h" /* for rrand(), rrand_seed() */
30 #include "rexit.h" /* for exit_game() */
31 #include "control.h" /* for meta_keys() */
32
33
34 int main(int argc, char *argv[])
35 {
36     struct World world;
37     char * recordfile = "record";
38     char * savefile = "savefile";
39     char * err_x = "A file 'record' exists, but no 'savefile'. If everything "
40                    "was in order, both or none would exist. I won't start "
41                    "until this is corrected.";
42     if (!access(recordfile, F_OK) && access(savefile, F_OK))
43     {
44         errno = 0;
45         exit_err(1, &world, err_x);
46     }
47     err_x        = "A 'savefile' exists, but no file 'record'. If everything "
48                    "was in order, both or none would exist. I won't start "
49                    "until this is corrected.";
50
51     if (!access(savefile, F_OK) && access(recordfile, F_OK))
52     {
53         errno = 0;
54         exit_err(1, &world, err_x);
55     }
56     char * recordfile_tmp = "record_tmp";
57     char * savefile_tmp   = "savefile_tmp";
58     err_x        = "A file 'recordfile_tmp' exists, probably from a corrupted "
59                    "previous record saving process. To avoid game record "
60                    "corruption, I won't start until it is removed or renamed.";
61     exit_err(!access(recordfile_tmp, F_OK), &world, err_x);
62     err_x        = "A file 'savefile_tmp' exists, probably from a corrupted "
63                    "previous game saving process. To avoid savegame "
64                    "corruption, I won't start until it is removed or renamed.";
65     exit_err(!access(savefile_tmp,   F_OK), &world, err_x);
66
67     /* Read in startup options (i.e. replay option and replay start turn). */
68     int opt;
69     uint32_t start_turn;
70     world.interactive = 1;
71     while ((opt = getopt(argc, argv, "s::")) != -1)
72     {
73         switch (opt)
74         {
75             case 's':
76                 world.interactive = 0;
77                 start_turn = 0;
78                 if (optarg)
79                     start_turn = atoi(optarg);
80                  break;
81             default:
82                 exit(EXIT_FAILURE);
83         }
84     }
85
86     /* Initialize log, player, monster/item definitions and monsters/items. */
87     world.log = calloc(1, sizeof(char));
88     set_cleanup_flag(CLEANUP_LOG);
89     update_log (&world, " ");
90     struct Player player;
91     player.hitpoints = 5;
92     world.player = &player;
93     world.monster = 0;
94     world.item = 0;
95     init_map_object_defs(&world, "defs");
96
97     /* For interactive mode, try to load world state from savefile. */
98     char * err_o = "Trouble loading game (fopen() in main()) / "
99                    "opening 'savefile' for reading.";
100     char * err_r = "Trouble loading game (in main()) / "
101                    "reading from opened 'savefile'.";
102     char * err_c = "Trouble loading game (fclose() in main()) / "
103                    "closing opened 'savefile'.";
104     FILE * file;
105     if (1 == world.interactive && 0 == access(savefile, F_OK))
106     {
107         file = fopen(savefile, "r");
108         exit_err(0 == file, &world, err_o);
109         if (   read_uint32_bigendian(file, &world.seed)
110             || read_uint32_bigendian(file, &world.turn)
111             || read_uint16_bigendian(file, &world.score)
112             || read_uint16_bigendian(file, &player.pos.y)
113             || read_uint16_bigendian(file, &player.pos.x)
114             || read_uint8(file, &player.hitpoints)
115             || read_map_objects(&world, &world.monster, file)
116             || read_map_objects(&world, &world.item,    file))
117         {
118             exit_err(1, &world, err_r);
119         }
120         exit_err(fclose(file), &world, err_c);
121         player.pos.y--;
122         player.pos.x--;
123     }
124
125     /* For non-interactive mode, try to load world state from record file. */
126     else
127     {
128         err_o = "Trouble loading record file (fopen() in main()) / "
129                 "opening file 'record' for reading.";
130         err_r = "Trouble loading record file (read_uint32_bigendian() in "
131                 "main()) / reading from opened file 'record'.";
132         world.turn = 1;
133         if (0 == world.interactive)
134         {
135             file = fopen(recordfile, "r");
136             exit_err(NULL == file, &world, err_o);
137             exit_err(read_uint32_bigendian(file, &world.seed), &world, err_r);
138         }
139
140         /* For interactive-mode in newly started world, generate a start seed
141          * from the current time.
142          */
143         else
144         {
145             world.seed = time(NULL);
146             world.score = 0;
147
148             err_o        = "Trouble recording new seed (fopen() in main()) / "
149                            "opening 'record_tmp' file for writing.";
150             char * err_w = "Trouble recording new seed "
151                            "(write_uint32_bigendian() in main()) / writing to "
152                            "opened file 'record_tmp'.";
153             err_c        = "Trouble recording new seed (fclose() in main()) / "
154                            "closing opened file 'record_tmp'.";
155             char * err_m = "Trouble recording new seed (rename() in main()) : "
156                            "renaming file 'record_tmp' to 'record'.";
157             file = fopen(recordfile_tmp, "w");
158             exit_err(0 == file, &world, err_o);
159             exit_err(write_uint32_bigendian(world.seed, file), &world, err_w);
160             exit_err(fclose(file), &world, err_c);
161             exit_err(rename(recordfile_tmp, recordfile), &world, err_m);
162         }
163     }
164
165
166     /* Generate map from seed and, if newly generated world, start positions of
167      * actors.
168      */
169     rrand_seed(world.seed);
170     struct Map map = init_map();
171     world.map = &map;
172     set_cleanup_flag(CLEANUP_MAP);
173     if (1 == world.turn)
174     {
175         player.pos = find_passable_pos(world.map);
176         void * foo;
177         foo = build_map_objects(&world, &world.monster, 1, 1 + rrand() % 27);
178         foo = build_map_objects(&world, foo, 2, 1 + rrand() % 9);
179         build_map_objects(&world, foo, 3, 1 + rrand() % 3);
180         foo = build_map_objects(&world, &world.item, 4, 1 + rrand() % 3);
181         build_map_objects(&world, foo, 5, 1 + rrand() % 3);
182     }
183
184     /* Initialize window system and windows. */
185     WINDOW * screen = initscr();
186     set_cleanup_flag(CLEANUP_NCURSES);
187     noecho();
188     curs_set(0);
189     keypad(screen, TRUE);
190     raw();
191     init_keybindings(&world);
192     set_cleanup_flag(CLEANUP_KEYBINDINGS);
193     struct WinMeta win_meta;
194     char * err_winmem = "Trouble with init_win:meta() or draw_all_wins() in "
195                         "main().";
196     exit_err(init_win_meta(screen, &win_meta), &world, err_winmem);
197     world.wins.meta = &win_meta;
198     struct Win win_keys = init_win(&win_meta, "Keys",
199                                      0, 29, &world, draw_keys_win);
200     world.wins.keys = &win_keys;
201     struct Win win_info = init_win(&win_meta, "Info",
202                                    3, 20, &world, draw_info_win);
203     world.wins.info = &win_info;
204     uint16_t height_logwin = win_meta.padframe.size.y
205                              - (2 + win_info.frame.size.y);
206     struct Win win_log = init_win(&win_meta, "Log",
207                                   height_logwin, 20, &world, draw_log_win);
208     world.wins.log = &win_log;
209     uint16_t width_mapwin = win_meta.padframe.size.x - win_keys.frame.size.x
210                             - win_log.frame.size.x - 2;
211     struct Win win_map = init_win(&win_meta, "Map",
212                                   0, width_mapwin, &world, draw_map_win);
213     world.wins.map = &win_map;
214     toggle_window(&win_meta, world.wins.keys);
215     toggle_window(&win_meta, world.wins.map);
216     toggle_window(&win_meta, world.wins.info);
217     toggle_window(&win_meta, world.wins.log);
218
219     /* Replay mode. */
220     int key;
221     uint8_t quit_called = 0;
222     uint8_t await_actions = 1;
223     if (0 == world.interactive)
224     {
225         int action;
226         while (1)
227         {
228             if (start_turn == world.turn)
229             {
230                 start_turn = 0;
231             }
232             if (0 == start_turn)
233             {
234                 exit_err(draw_all_wins(&win_meta), &world, err_winmem);
235                 key = getch();
236             }
237             if (1 == await_actions
238                 && (world.turn < start_turn
239                     || key == get_action_key(world.keybindings,
240                                              "wait / next turn")) )
241             {
242                 action = getc(file);
243                 if (EOF == action)
244                 {
245                     start_turn = 0;
246                     await_actions = 0;
247                 }
248                 else if (0 == action)
249                 {
250                     player_wait (&world);
251                 }
252                 else if (NORTH == action)
253                 {
254                     move_player(&world, NORTH);
255                 }
256                 else if (EAST  == action)
257                 {
258                     move_player(&world, EAST);
259                 }
260                 else if (SOUTH == action)
261                 {
262                     move_player(&world, SOUTH);
263                 }
264                 else if (WEST == action)
265                 {
266                     move_player(&world, WEST);
267                 }
268             }
269             else
270             {
271                 quit_called = meta_keys(key, &world);
272                 if (1 == quit_called)
273                 {
274                     err_c = "Trouble closing 'record' file (fclose() in "
275                             "main()).";
276                     exit_err(fclose(file), &world, err_c);
277                     exit_game(&world);
278                 }
279             }
280         }
281     }
282
283     /* Interactive mode. */
284     else
285     {
286         uint32_t last_turn = 0;
287         while (1)
288         {
289             if (last_turn != world.turn)
290             {
291                 save_game(&world);
292                 last_turn = world.turn;
293             }
294             if (1 == await_actions && 0 == player.hitpoints)
295             {
296                 await_actions = 0;
297             }
298             draw_all_wins (&win_meta);
299             key = getch();
300             if      (1 == await_actions
301                      && key == get_action_key(world.keybindings,
302                                               "player up"))
303             {
304                 move_player(&world, NORTH);
305             }
306             else if (1 == await_actions
307                      && key == get_action_key(world.keybindings,
308                                               "player right"))
309             {
310                 move_player(&world, EAST);
311             }
312             else if (1 == await_actions
313                      && key == get_action_key(world.keybindings,
314                                               "player down"))
315             {
316                 move_player(&world, SOUTH);
317             }
318             else if (1 == await_actions
319                      && key == get_action_key(world.keybindings,
320                                               "player left"))
321             {
322                 move_player(&world, WEST);
323             }
324             else if (1 == await_actions
325                      && key == get_action_key(world.keybindings,
326                                               "wait / next turn"))
327             {
328                 player_wait (&world);
329             }
330             else
331             {
332                 quit_called = meta_keys(key, &world);
333                 if (1 == quit_called)
334                 {
335                     exit_game(&world);
336                 }
337             }
338         }
339     }
340 }