1 /*
2  *  platformdependent.c
3  *  Brogue
4  *
5  *  Created by Brian Walker on 4/13/10.
6  *  Copyright 2010. All rights reserved.
7  *
8  *  This file is part of Brogue.
9  *
10  *  Brogue is free software: you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License as published by
12  *  the Free Software Foundation, either version 3 of the License, or
13  *  (at your option) any later version.
14  *
15  *  Brogue is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU General Public License for more details.
19  *
20  *  You should have received a copy of the GNU General Public License
21  *  along with Brogue.  If not, see <http://www.gnu.org/licenses/>.
22  */
23 
24 #include <ctype.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <time.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <dirent.h>
31 
32 #include "platform.h"
33 
34 typedef struct brogueScoreEntry {
35     long int score;
36     long int dateNumber; // in seconds
37     char dateText[COLS]; // in the form mm/dd/yy
38     char description[COLS];
39 } brogueScoreEntry;
40 
41 brogueScoreEntry scoreBuffer[HIGH_SCORES_COUNT];
42 
glyphToUnicode(enum displayGlyph glyph)43 unsigned int glyphToUnicode(enum displayGlyph glyph) {
44     if (glyph < 128) return glyph;
45 
46     switch (glyph) {
47         case G_UP_ARROW: return U_UP_ARROW;
48         case G_DOWN_ARROW: return U_DOWN_ARROW;
49         case G_POTION: return '!';
50         case G_GRASS: return '"';
51         case G_WALL: return '#';
52         case G_DEMON: return '&';
53         case G_OPEN_DOOR: return '\'';
54         case G_GOLD: return '*';
55         case G_CLOSED_DOOR: return '+';
56         case G_RUBBLE: return ',';
57         case G_KEY: return '-';
58         case G_BOG: return '~';
59         case G_CHAIN_TOP_LEFT:
60         case G_CHAIN_BOTTOM_RIGHT:
61             return '\\';
62         case G_CHAIN_TOP_RIGHT:
63         case G_CHAIN_BOTTOM_LEFT:
64             return '/';
65         case G_CHAIN_TOP:
66         case G_CHAIN_BOTTOM:
67             return '|';
68         case G_CHAIN_LEFT:
69         case G_CHAIN_RIGHT:
70             return '-';
71         case G_FOOD: return ';';
72         case G_UP_STAIRS: return '<';
73         case G_VENT: return '=';
74         case G_DOWN_STAIRS: return '>';
75         case G_PLAYER: return '@';
76         case G_BOG_MONSTER: return 'B';
77         case G_CENTAUR: return 'C';
78         case G_DRAGON: return 'D';
79         case G_FLAMEDANCER: return 'F';
80         case G_GOLEM: return 'G';
81         case G_TENTACLE_HORROR: return 'H';
82         case G_IFRIT: return 'I';
83         case G_JELLY: return 'J';
84         case G_KRAKEN: return 'K';
85         case G_LICH: return 'L';
86         case G_NAGA: return 'N';
87         case G_OGRE: return 'O';
88         case G_PHANTOM: return 'P';
89         case G_REVENANT: return 'R';
90         case G_SALAMANDER: return 'S';
91         case G_TROLL: return 'T';
92         case G_UNDERWORM: return 'U';
93         case G_VAMPIRE: return 'V';
94         case G_WRAITH: return 'W';
95         case G_ZOMBIE: return 'Z';
96         case G_ARMOR: return '[';
97         case G_STAFF: return '/';
98         case G_WEB: return ':';
99         case G_MOUND: return 'a';
100         case G_BLOAT: return 'b';
101         case G_CENTIPEDE: return 'c';
102         case G_DAR_BLADEMASTER: return 'd';
103         case G_EEL: return 'e';
104         case G_FURY: return 'f';
105         case G_GOBLIN: return 'g';
106         case G_IMP: return 'i';
107         case G_JACKAL: return 'j';
108         case G_KOBOLD: return 'k';
109         case G_MONKEY: return 'm';
110         case G_PIXIE: return 'p';
111         case G_RAT: return 'r';
112         case G_SPIDER: return 's';
113         case G_TOAD: return 't';
114         case G_BAT: return 'v';
115         case G_WISP: return 'w';
116         case G_PHOENIX: return 'P';
117         case G_ALTAR: return '|';
118         case G_LIQUID: return '~';
119         case G_FLOOR: return U_MIDDLE_DOT;
120         case G_CHASM: return U_FOUR_DOTS;
121         case G_TRAP: return U_DIAMOND;
122         case G_FIRE: return U_FLIPPED_V;
123         case G_FOLIAGE: return U_ARIES;
124         case G_AMULET: return U_ANKH;
125         case G_SCROLL: return U_MUSIC_NOTE;
126         case G_RING: return U_CIRCLE;
127         case G_WEAPON: return U_UP_ARROW;
128         case G_GEM: return U_FILLED_CIRCLE;
129         case G_TOTEM: return U_NEUTER;
130         case G_GOOD_MAGIC: return U_FILLED_CIRCLE_BARS;
131         case G_BAD_MAGIC: return U_CIRCLE_BARS;
132         case G_DOORWAY: return U_OMEGA;
133         case G_CHARM: return U_LIGHTNING_BOLT;
134         case G_WALL_TOP: return '#';
135         case G_DAR_PRIESTESS: return 'd';
136         case G_DAR_BATTLEMAGE: return 'd';
137         case G_GOBLIN_MAGIC: return 'g';
138         case G_GOBLIN_CHIEFTAN: return 'g';
139         case G_OGRE_MAGIC: return 'O';
140         case G_GUARDIAN: return U_ESZETT;
141         case G_WINGED_GUARDIAN: return U_ESZETT;
142         case G_EGG: return U_FILLED_CIRCLE;
143         case G_WARDEN: return 'Y';
144         case G_DEWAR: return '&';
145         case G_ANCIENT_SPIRIT: return 'M';
146         case G_LEVER: return '/';
147         case G_LEVER_PULLED: return '\\';
148         case G_BLOODWORT_STALK: return U_ARIES;
149         case G_FLOOR_ALT: return U_MIDDLE_DOT;
150         case G_UNICORN: return U_U_ACUTE;
151         case G_TURRET: return U_FILLED_CIRCLE;
152         case G_WAND: return '~';
153         case G_GRANITE: return '#';
154         case G_CARPET: return U_MIDDLE_DOT;
155         case G_CLOSED_IRON_DOOR: return '+';
156         case G_OPEN_IRON_DOOR: return '\'';
157         case G_TORCH: return '#';
158         case G_CRYSTAL: return '#';
159         case G_PORTCULLIS: return '#';
160         case G_BARRICADE: return '#';
161         case G_STATUE: return U_ESZETT;
162         case G_CRACKED_STATUE: return U_ESZETT;
163         case G_CLOSED_CAGE: return '#';
164         case G_OPEN_CAGE: return '|';
165         case G_PEDESTAL: return '|';
166         case G_CLOSED_COFFIN: return '-';
167         case G_OPEN_COFFIN: return '-';
168         case G_MAGIC_GLYPH: return U_FOUR_DOTS;
169         case G_BRIDGE: return '=';
170         case G_BONES: return ',';
171         case G_ELECTRIC_CRYSTAL: return U_CURRENCY;
172         case G_ASHES: return '\'';
173         case G_BEDROLL: return '=';
174         case G_BLOODWORT_POD: return '*';
175         case G_VINE: return ':';
176         case G_NET: return ':';
177         case G_LICHEN: return '"';
178         case G_PIPES: return '+';
179         case G_SAC_ALTAR: return '|';
180         case G_ORB_ALTAR: return '|';
181 
182         default:
183             brogueAssert(false);
184             return '?';
185     }
186 }
187 
188 /*
189 Tells if a glyph represents part of the environment (true) or an item or creature (false).
190 */
isEnvironmentGlyph(enum displayGlyph glyph)191 boolean isEnvironmentGlyph(enum displayGlyph glyph) {
192     switch (glyph) {
193         // items
194         case G_AMULET: case G_ARMOR: case G_BEDROLL: case G_CHARM:
195         case G_DEWAR: case G_EGG: case G_FOOD: case G_GEM: case G_BLOODWORT_POD:
196         case G_GOLD: case G_KEY: case G_POTION: case G_RING:
197         case G_SCROLL: case G_STAFF: case G_WAND: case G_WEAPON:
198             return false;
199 
200         // creatures
201         case G_ANCIENT_SPIRIT: case G_BAT: case G_BLOAT: case G_BOG_MONSTER:
202         case G_CENTAUR: case G_CENTIPEDE: case G_DAR_BATTLEMAGE: case G_DAR_BLADEMASTER:
203         case G_DAR_PRIESTESS: case G_DEMON: case G_DRAGON: case G_EEL:
204         case G_FLAMEDANCER: case G_FURY: case G_GOBLIN: case G_GOBLIN_CHIEFTAN:
205         case G_GOBLIN_MAGIC: case G_GOLEM: case G_GUARDIAN: case G_IFRIT:
206         case G_IMP: case G_JACKAL: case G_JELLY: case G_KOBOLD:
207         case G_KRAKEN: case G_LICH: case G_MONKEY: case G_MOUND:
208         case G_NAGA: case G_OGRE: case G_OGRE_MAGIC: case G_PHANTOM:
209         case G_PHOENIX: case G_PIXIE: case G_PLAYER: case G_RAT:
210         case G_REVENANT: case G_SALAMANDER: case G_SPIDER: case G_TENTACLE_HORROR:
211         case G_TOAD: case G_TROLL: case G_UNDERWORM: case G_UNICORN:
212         case G_VAMPIRE: case G_WARDEN: case G_WINGED_GUARDIAN: case G_WISP:
213         case G_WRAITH: case G_ZOMBIE:
214             return false;
215 
216         // everything else is considered part of the environment
217         default:
218             return true;
219     }
220 }
221 
plotChar(enum displayGlyph inputChar,short xLoc,short yLoc,short foreRed,short foreGreen,short foreBlue,short backRed,short backGreen,short backBlue)222 void plotChar(enum displayGlyph inputChar,
223               short xLoc, short yLoc,
224               short foreRed, short foreGreen, short foreBlue,
225               short backRed, short backGreen, short backBlue) {
226     currentConsole.plotChar(inputChar, xLoc, yLoc, foreRed, foreGreen, foreBlue, backRed, backGreen, backBlue);
227 }
228 
pausingTimerStartsNow()229 void pausingTimerStartsNow() {
230 
231 }
232 
shiftKeyIsDown()233 boolean shiftKeyIsDown() {
234     return currentConsole.modifierHeld(0);
235 }
controlKeyIsDown()236 boolean controlKeyIsDown() {
237     return currentConsole.modifierHeld(1);
238 }
239 
nextKeyOrMouseEvent(rogueEvent * returnEvent,boolean textInput,boolean colorsDance)240 void nextKeyOrMouseEvent(rogueEvent *returnEvent, boolean textInput, boolean colorsDance) {
241     currentConsole.nextKeyOrMouseEvent(returnEvent, textInput, colorsDance);
242 }
243 
pauseForMilliseconds(short milliseconds)244 boolean pauseForMilliseconds(short milliseconds) {
245     return currentConsole.pauseForMilliseconds(milliseconds);
246 }
247 
notifyEvent(short eventId,int data1,int data2,const char * str1,const char * str2)248 void notifyEvent(short eventId, int data1, int data2, const char *str1, const char *str2) {
249     if (currentConsole.notifyEvent) {
250         currentConsole.notifyEvent(eventId, data1, data2, str1, str2);
251     }
252 }
253 
takeScreenshot()254 boolean takeScreenshot() {
255     if (currentConsole.takeScreenshot) {
256         return currentConsole.takeScreenshot();
257     } else {
258         return false;
259     }
260 }
261 
setGraphicsMode(enum graphicsModes mode)262 enum graphicsModes setGraphicsMode(enum graphicsModes mode) {
263     if (currentConsole.setGraphicsMode) {
264         return currentConsole.setGraphicsMode(mode);
265     } else {
266         return TEXT_GRAPHICS;
267     }
268 }
269 
270 // creates an empty high scores file
initScores()271 void initScores() {
272     short i;
273     FILE *scoresFile;
274 
275     scoresFile = fopen("BrogueHighScores.txt", "w");
276     for (i=0; i<HIGH_SCORES_COUNT; i++) {
277         fprintf(scoresFile, "%li\t%li\t%s", (long) 0, (long) 0, "(empty entry)\n");
278     }
279     fclose(scoresFile);
280 }
281 
282 // sorts the entries of the scoreBuffer global variable by score in descending order;
283 // returns the sorted line number of the most recent entry
sortScoreBuffer()284 short sortScoreBuffer() {
285     short i, j, highestUnsortedLine, mostRecentSortedLine = 0;
286     long highestUnsortedScore, mostRecentDate;
287     brogueScoreEntry sortedScoreBuffer[HIGH_SCORES_COUNT];
288     boolean lineSorted[HIGH_SCORES_COUNT];
289 
290     mostRecentDate = 0;
291 
292     for (i=0; i<HIGH_SCORES_COUNT; i++) {
293         lineSorted[i] = false;
294     }
295 
296     for (i=0; i<HIGH_SCORES_COUNT; i++) {
297         highestUnsortedLine = 0;
298         highestUnsortedScore = 0;
299         for (j=0; j<HIGH_SCORES_COUNT; j++) {
300             if (!lineSorted[j] && scoreBuffer[j].score >= highestUnsortedScore) {
301                 highestUnsortedLine = j;
302                 highestUnsortedScore = scoreBuffer[j].score;
303             }
304         }
305         sortedScoreBuffer[i] = scoreBuffer[highestUnsortedLine];
306         lineSorted[highestUnsortedLine] = true;
307     }
308 
309     // copy the sorted list back into scoreBuffer, remember the most recent entry
310     for (i=0; i<HIGH_SCORES_COUNT; i++) {
311         scoreBuffer[i] = sortedScoreBuffer[i];
312         if (scoreBuffer[i].dateNumber > mostRecentDate) {
313             mostRecentDate = scoreBuffer[i].dateNumber;
314             mostRecentSortedLine = i;
315         }
316     }
317     return mostRecentSortedLine;
318 }
319 
320 // loads the BrogueHighScores.txt file into the scoreBuffer global variable
321 // score file format is: score, tab, date in seconds, tab, description, newline.
loadScoreBuffer()322 short loadScoreBuffer() {
323     short i;
324     FILE *scoresFile;
325     time_t rawtime;
326     struct tm * timeinfo;
327 
328     scoresFile = fopen("BrogueHighScores.txt", "r");
329 
330     if (scoresFile == NULL) {
331         initScores();
332         scoresFile = fopen("BrogueHighScores.txt", "r");
333     }
334 
335     for (i=0; i<HIGH_SCORES_COUNT; i++) {
336         // load score and also the date in seconds
337         fscanf(scoresFile, "%li\t%li\t", &(scoreBuffer[i].score), &(scoreBuffer[i].dateNumber));
338 
339         // load description
340         fgets(scoreBuffer[i].description, COLS, scoresFile);
341         // strip the newline off the end
342         scoreBuffer[i].description[strlen(scoreBuffer[i].description) - 1] = '\0';
343 
344         // convert date to DATE_FORMAT
345         rawtime = (time_t) scoreBuffer[i].dateNumber;
346         timeinfo = localtime(&rawtime);
347         strftime(scoreBuffer[i].dateText, DCOLS, DATE_FORMAT, timeinfo);
348     }
349     fclose(scoresFile);
350     return sortScoreBuffer();
351 }
352 
loadKeymap()353 void loadKeymap() {
354     int i;
355     FILE *f;
356     char buffer[512];
357 
358     f = fopen("keymap.txt", "r");
359 
360     if (f != NULL) {
361         while (fgets(buffer, 512, f) != NULL) {
362             // split it in two (destructively)
363             int mode = 1;
364             char *input_name = NULL, *output_name = NULL;
365             for (i = 0; buffer[i]; i++) {
366                 if (isspace(buffer[i])) {
367                     buffer[i] = '\0';
368                     mode = 1;
369                 } else {
370                     if (mode) {
371                         if (input_name == NULL) input_name = buffer + i;
372                         else if (output_name == NULL) output_name = buffer + i;
373                     }
374                     mode = 0;
375                 }
376             }
377             if (input_name != NULL && output_name != NULL) {
378                 if (input_name[0] == '#') continue; // must be a comment
379 
380                 currentConsole.remap(input_name, output_name);
381             }
382         }
383         fclose(f);
384     }
385 }
386 
387 
388 // saves the scoreBuffer global variable into the BrogueHighScores.txt file,
389 // thus overwriting whatever is already there.
390 // The numerical version of the date is what gets saved; the "mm/dd/yy" version is ignored.
391 // Does NOT do any sorting.
saveScoreBuffer()392 void saveScoreBuffer() {
393     short i;
394     FILE *scoresFile;
395 
396     scoresFile = fopen("BrogueHighScores.txt", "w");
397 
398     for (i=0; i<HIGH_SCORES_COUNT; i++) {
399         // save the entry
400         fprintf(scoresFile, "%li\t%li\t%s\n", scoreBuffer[i].score, scoreBuffer[i].dateNumber, scoreBuffer[i].description);
401     }
402 
403     fclose(scoresFile);
404 }
405 
dumpScores()406 void dumpScores() {
407     int i;
408 
409     rogueHighScoresEntry list[HIGH_SCORES_COUNT];
410     getHighScoresList(list);
411 
412     for (i = 0; i < HIGH_SCORES_COUNT; i++) {
413         if (list[i].score > 0) {
414             printf("%d\t%s\t%s\n", (int) list[i].score, list[i].date, list[i].description);
415         }
416     }
417 }
418 
getHighScoresList(rogueHighScoresEntry returnList[HIGH_SCORES_COUNT])419 short getHighScoresList(rogueHighScoresEntry returnList[HIGH_SCORES_COUNT]) {
420     short i, mostRecentLineNumber;
421 
422     mostRecentLineNumber = loadScoreBuffer();
423 
424     for (i=0; i<HIGH_SCORES_COUNT; i++) {
425         returnList[i].score =               scoreBuffer[i].score;
426         strcpy(returnList[i].date,          scoreBuffer[i].dateText);
427         strcpy(returnList[i].description,   scoreBuffer[i].description);
428     }
429 
430     return mostRecentLineNumber;
431 }
432 
saveHighScore(rogueHighScoresEntry theEntry)433 boolean saveHighScore(rogueHighScoresEntry theEntry) {
434     short i, lowestScoreIndex = -1;
435     long lowestScore = -1;
436 
437     loadScoreBuffer();
438 
439     for (i=0; i<HIGH_SCORES_COUNT; i++) {
440         if (scoreBuffer[i].score < lowestScore || i == 0) {
441             lowestScore = scoreBuffer[i].score;
442             lowestScoreIndex = i;
443         }
444     }
445 
446     if (lowestScore > theEntry.score) {
447         return false;
448     }
449 
450     scoreBuffer[lowestScoreIndex].score =               theEntry.score;
451     scoreBuffer[lowestScoreIndex].dateNumber =          (long) time(NULL);
452     strcpy(scoreBuffer[lowestScoreIndex].description,   theEntry.description);
453 
454     saveScoreBuffer();
455 
456     return true;
457 }
458 
459 // start of file listing
460 
461 struct filelist {
462     fileEntry *files;
463     char *names;
464 
465     int nfiles, maxfiles;
466     int nextname, maxname;
467 };
468 
newFilelist()469 struct filelist *newFilelist() {
470     struct filelist *list = malloc(sizeof(*list));
471 
472     list->nfiles = 0;
473     list->nextname = 0;
474     list->maxfiles = 64;
475     list->maxname = list->maxfiles * 64;
476 
477     list->files = malloc(sizeof(fileEntry) * list->maxfiles);
478     list->names = malloc(list->maxname);
479 
480     return list;
481 }
482 
addfile(struct filelist * list,const char * name)483 fileEntry *addfile(struct filelist *list, const char *name) {
484     int len = strlen(name);
485     if (len + list->nextname >= list->maxname) {
486         int newmax = (list->maxname + len) * 2;
487         char *newnames = realloc(list->names, newmax);
488         if (newnames != NULL) {
489             list->names = newnames;
490             list->maxname = newmax;
491         } else {
492             // fail silently
493             return NULL;
494         }
495     }
496 
497     if (list->nfiles >= list->maxfiles) {
498         int newmax = list->maxfiles * 2;
499         fileEntry *newfiles = realloc(list->files, sizeof(fileEntry) * newmax);
500         if (newfiles != NULL) {
501             list->files = newfiles;
502             list->maxfiles = newmax;
503         } else {
504             // fail silently
505             return NULL;
506         }
507     }
508 
509     // add the new file and copy the name into the buffer
510     list->files[list->nfiles].path = ((char *) NULL) + list->nextname; // don't look at them until they are transferred out
511     list->files[list->nfiles].date = (struct tm) {0}; // associate a dummy date (1899-12-31) to avoid random data, it will be correctly populated when using listFiles()
512 
513     strncpy(list->names + list->nextname, name, len + 1);
514 
515     list->nextname += len + 1;
516     list->nfiles += 1;
517 
518     return list->files + (list->nfiles - 1);
519 }
520 
freeFilelist(struct filelist * list)521 void freeFilelist(struct filelist *list) {
522     //if (list->names != NULL) free(list->names);
523     //if (list->files != NULL) free(list->files);
524     free(list);
525 }
526 
commitFilelist(struct filelist * list,char ** namebuffer)527 fileEntry *commitFilelist(struct filelist *list, char **namebuffer) {
528     int i;
529     /*fileEntry *files = malloc(list->nfiles * sizeof(fileEntry) + list->nextname); // enough space for all the names and all the files
530 
531     if (files != NULL) {
532         char *names = (char *) (files + list->nfiles);
533 
534         for (i=0; i < list->nfiles; i++) {
535             files[i] = list->files[i];
536             files[i].path = names + (files[i].path - (char *) NULL);
537         }
538 
539         memcpy(names, list->names, list->nextname);
540     }
541     */
542     for (i=0; i < list->nfiles; i++) {
543         list->files[i].path = list->names + (list->files[i].path - (char *) NULL);
544     }
545     *namebuffer = list->names;
546 
547     return list->files;
548 }
549 
listFiles(short * fileCount,char ** namebuffer)550 fileEntry *listFiles(short *fileCount, char **namebuffer) {
551     struct filelist *list = newFilelist();
552 
553     // windows: FindFirstFile/FindNextFile
554     DIR *dp= opendir ("./");
555 
556     if (dp != NULL) {
557         struct dirent *ep;
558         struct stat statbuf;
559         struct tm *timeinfo;
560 
561         while ((ep = readdir(dp))) {
562             // get statistics about the file (0 on success)
563             if (!stat(ep->d_name, &statbuf)) {
564                 fileEntry *file = addfile(list, ep->d_name);
565                 if (file != NULL) {
566                     // add the modification date to the file entry
567                     timeinfo = localtime(&statbuf.st_mtime);
568                     file->date = *timeinfo;
569                 }
570             }
571         }
572 
573         closedir (dp);
574     }
575     else {
576         *fileCount = 0;
577         return NULL;
578     }
579 
580     fileEntry *files = commitFilelist(list, namebuffer);
581 
582     if (files != NULL) {
583         *fileCount = (short) list->nfiles;
584     } else {
585         *fileCount = 0;
586     }
587 
588     freeFilelist(list);
589 
590     return files;
591 }
592 
593 // end of file listing
594 
initializeLaunchArguments(enum NGCommands * command,char * path,uint64_t * seed)595 void initializeLaunchArguments(enum NGCommands *command, char *path, uint64_t *seed) {
596     // we've actually already done this at this point, except for the seed.
597 }
598 
isApplicationActive(void)599 boolean isApplicationActive(void) {
600     // FIXME: finish
601     return true;
602 }
603 
604