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