Annotation of brogue-ce/src/brogue/MainMenu.c, Revision 1.1
1.1 ! rubenllo 1: /*
! 2: * Buttons.c
! 3: * Brogue
! 4: *
! 5: * Created by Brian Walker on 1/14/12.
! 6: * Copyright 2012. All rights reserved.
! 7: *
! 8: * This file is part of Brogue.
! 9: *
! 10: * This program is free software: you can redistribute it and/or modify
! 11: * it under the terms of the GNU Affero General Public License as
! 12: * published by the Free Software Foundation, either version 3 of the
! 13: * License, or (at your option) any later version.
! 14: *
! 15: * This program 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 Affero General Public License for more details.
! 19: *
! 20: * You should have received a copy of the GNU Affero General Public License
! 21: * along with this program. If not, see <http://www.gnu.org/licenses/>.
! 22: */
! 23:
! 24: #include "Rogue.h"
! 25: #include "IncludeGlobals.h"
! 26: #include <time.h>
! 27: #include <limits.h>
! 28:
! 29: #define MENU_FLAME_PRECISION_FACTOR 10
! 30: #define MENU_FLAME_RISE_SPEED 50
! 31: #define MENU_FLAME_SPREAD_SPEED 20
! 32: #define MENU_FLAME_COLOR_DRIFT_SPEED 500
! 33: #define MENU_FLAME_FADE_SPEED 20
! 34: #define MENU_FLAME_UPDATE_DELAY 50
! 35: #define MENU_FLAME_ROW_PADDING 2
! 36: #define MENU_TITLE_OFFSET_X (-4)
! 37: #define MENU_TITLE_OFFSET_Y (-1)
! 38:
! 39: #define MENU_FLAME_COLOR_SOURCE_COUNT 1136
! 40:
! 41: #define MENU_FLAME_DENOMINATOR (100 + MENU_FLAME_RISE_SPEED + MENU_FLAME_SPREAD_SPEED)
! 42:
! 43:
! 44: void drawMenuFlames(signed short flames[COLS][(ROWS + MENU_FLAME_ROW_PADDING)][3], unsigned char mask[COLS][ROWS]) {
! 45: short i, j, versionStringLength;
! 46: color tempColor = {0};
! 47: const color *maskColor = &black;
! 48: char dchar;
! 49:
! 50: versionStringLength = strLenWithoutEscapes(BROGUE_VERSION_STRING);
! 51:
! 52: for (j=0; j<ROWS; j++) {
! 53: for (i=0; i<COLS; i++) {
! 54: if (j == ROWS - 1 && i >= COLS - versionStringLength) {
! 55: dchar = BROGUE_VERSION_STRING[i - (COLS - versionStringLength)];
! 56: } else {
! 57: dchar = ' ';
! 58: }
! 59:
! 60: if (mask[i][j] == 100) {
! 61: plotCharWithColor(dchar, i, j, &veryDarkGray, maskColor);
! 62: } else {
! 63: tempColor = black;
! 64: tempColor.red = flames[i][j][0] / MENU_FLAME_PRECISION_FACTOR;
! 65: tempColor.green = flames[i][j][1] / MENU_FLAME_PRECISION_FACTOR;
! 66: tempColor.blue = flames[i][j][2] / MENU_FLAME_PRECISION_FACTOR;
! 67: if (mask[i][j] > 0) {
! 68: applyColorAverage(&tempColor, maskColor, mask[i][j]);
! 69: }
! 70: plotCharWithColor(dchar, i, j, &veryDarkGray, &tempColor);
! 71: }
! 72: }
! 73: }
! 74: }
! 75:
! 76: void updateMenuFlames(const color *colors[COLS][(ROWS + MENU_FLAME_ROW_PADDING)],
! 77: signed short colorSources[MENU_FLAME_COLOR_SOURCE_COUNT][4],
! 78: signed short flames[COLS][(ROWS + MENU_FLAME_ROW_PADDING)][3]) {
! 79:
! 80: short i, j, k, l, x, y;
! 81: signed short tempFlames[COLS][3];
! 82: short colorSourceNumber, rand;
! 83:
! 84: colorSourceNumber = 0;
! 85: for (j=0; j<(ROWS + MENU_FLAME_ROW_PADDING); j++) {
! 86: // Make a temp copy of the current row.
! 87: for (i=0; i<COLS; i++) {
! 88: for (k=0; k<3; k++) {
! 89: tempFlames[i][k] = flames[i][j][k];
! 90: }
! 91: }
! 92:
! 93: for (i=0; i<COLS; i++) {
! 94: // Each cell is the weighted average of the three color values below and itself.
! 95: // Weight of itself: 100
! 96: // Weight of left and right neighbors: MENU_FLAME_SPREAD_SPEED / 2 each
! 97: // Weight of below cell: MENU_FLAME_RISE_SPEED
! 98: // Divisor: 100 + MENU_FLAME_SPREAD_SPEED + MENU_FLAME_RISE_SPEED
! 99:
! 100: // Itself:
! 101: for (k=0; k<3; k++) {
! 102: flames[i][j][k] = 100 * flames[i][j][k] / MENU_FLAME_DENOMINATOR;
! 103: }
! 104:
! 105: // Left and right neighbors:
! 106: for (l = -1; l <= 1; l += 2) {
! 107: x = i + l;
! 108: if (x == -1) {
! 109: x = COLS - 1;
! 110: } else if (x == COLS) {
! 111: x = 0;
! 112: }
! 113: for (k=0; k<3; k++) {
! 114: flames[i][j][k] += MENU_FLAME_SPREAD_SPEED * tempFlames[x][k] / 2 / MENU_FLAME_DENOMINATOR;
! 115: }
! 116: }
! 117:
! 118: // Below:
! 119: y = j + 1;
! 120: if (y < (ROWS + MENU_FLAME_ROW_PADDING)) {
! 121: for (k=0; k<3; k++) {
! 122: flames[i][j][k] += MENU_FLAME_RISE_SPEED * flames[i][y][k] / MENU_FLAME_DENOMINATOR;
! 123: }
! 124: }
! 125:
! 126: // Fade a little:
! 127: for (k=0; k<3; k++) {
! 128: flames[i][j][k] = (1000 - MENU_FLAME_FADE_SPEED) * flames[i][j][k] / 1000;
! 129: }
! 130:
! 131: if (colors[i][j]) {
! 132: // If it's a color source tile:
! 133:
! 134: // First, cause the color to drift a little.
! 135: for (k=0; k<4; k++) {
! 136: colorSources[colorSourceNumber][k] += rand_range(-MENU_FLAME_COLOR_DRIFT_SPEED, MENU_FLAME_COLOR_DRIFT_SPEED);
! 137: colorSources[colorSourceNumber][k] = clamp(colorSources[colorSourceNumber][k], 0, 1000);
! 138: }
! 139:
! 140: // Then, add the color to this tile's flames.
! 141: rand = colors[i][j]->rand * colorSources[colorSourceNumber][0] / 1000;
! 142: flames[i][j][0] += (colors[i][j]->red + (colors[i][j]->redRand * colorSources[colorSourceNumber][1] / 1000) + rand) * MENU_FLAME_PRECISION_FACTOR;
! 143: flames[i][j][1] += (colors[i][j]->green + (colors[i][j]->greenRand * colorSources[colorSourceNumber][2] / 1000) + rand) * MENU_FLAME_PRECISION_FACTOR;
! 144: flames[i][j][2] += (colors[i][j]->blue + (colors[i][j]->blueRand * colorSources[colorSourceNumber][3] / 1000) + rand) * MENU_FLAME_PRECISION_FACTOR;
! 145:
! 146: colorSourceNumber++;
! 147: }
! 148: }
! 149: }
! 150: }
! 151:
! 152: // Takes a grid of values, each of which is 0 or 100, and fills in some middle values in the interstices.
! 153: void antiAlias(unsigned char mask[COLS][ROWS]) {
! 154: short i, j, x, y, dir, nbCount;
! 155: const short intensity[5] = {0, 0, 35, 50, 60};
! 156:
! 157: for (i=0; i<COLS; i++) {
! 158: for (j=0; j<ROWS; j++) {
! 159: if (mask[i][j] < 100) {
! 160: nbCount = 0;
! 161: for (dir=0; dir<4; dir++) {
! 162: x = i + nbDirs[dir][0];
! 163: y = j + nbDirs[dir][1];
! 164: if (coordinatesAreInWindow(x, y) && mask[x][y] == 100) {
! 165: nbCount++;
! 166: }
! 167: }
! 168: mask[i][j] = intensity[nbCount];
! 169: }
! 170: }
! 171: }
! 172: }
! 173:
! 174: #define MENU_TITLE_WIDTH 74
! 175: #define MENU_TITLE_HEIGHT 19
! 176:
! 177: void initializeMenuFlames(boolean includeTitle,
! 178: const color *colors[COLS][(ROWS + MENU_FLAME_ROW_PADDING)],
! 179: color colorStorage[COLS],
! 180: signed short colorSources[MENU_FLAME_COLOR_SOURCE_COUNT][4],
! 181: signed short flames[COLS][(ROWS + MENU_FLAME_ROW_PADDING)][3],
! 182: unsigned char mask[COLS][ROWS]) {
! 183: short i, j, k, colorSourceCount;
! 184: const char title[MENU_TITLE_HEIGHT][MENU_TITLE_WIDTH+1] = {
! 185: "######## ######## ###### ####### #### ### #########",
! 186: " ## ### ## ### ## ### ## ## ## # ## #",
! 187: " ## ## ## ## ## ### ## # ## # ## #",
! 188: " ## ## ## ## # # ## # # ## # ## ",
! 189: " ## ## ## ## ## ## ## ## ## # ## # ",
! 190: " ## ## ## ## ## ### ## ## ## # ## # ",
! 191: " ###### ## ### ## #### ## ## ## # ####### ",
! 192: " ## ## ## ## ## #### ## ## ## # ## # ",
! 193: " ## ## ## ## ## ### ## ## ##### ## # ## # ",
! 194: " ## ## ## ## ### ## ## ### ## ## # ## ",
! 195: " ## ## ## ## ## # # ## ## ## # ## ",
! 196: " ## ## ## ## ### ## ### ## ### # ## #",
! 197: " ## ## ## ## ### ## ### ### ### # ## #",
! 198: "######## #### ### ###### ##### ###### #########",
! 199: " ## ",
! 200: " ########## ",
! 201: " ## ",
! 202: " ## ",
! 203: " #### ",
! 204: };
! 205:
! 206: for (i=0; i<COLS; i++) {
! 207: for (j=0; j<ROWS; j++) {
! 208: mask[i][j] = 0;
! 209: }
! 210: }
! 211:
! 212: for (i=0; i<COLS; i++) {
! 213: for (j=0; j<(ROWS + MENU_FLAME_ROW_PADDING); j++) {
! 214: colors[i][j] = NULL;
! 215: for (k=0; k<3; k++) {
! 216: flames[i][j][k] = 0;
! 217: }
! 218: }
! 219: }
! 220:
! 221: // Seed source color random components.
! 222: for (i=0; i<MENU_FLAME_COLOR_SOURCE_COUNT; i++) {
! 223: for (k=0; k<4; k++) {
! 224: colorSources[i][k] = rand_range(0, 1000);
! 225: }
! 226: }
! 227:
! 228: // Put some flame source along the bottom row.
! 229: colorSourceCount = 0;
! 230: for (i=0; i<COLS; i++) {
! 231: colorStorage[colorSourceCount] = flameSourceColor;
! 232: applyColorAverage(&(colorStorage[colorSourceCount]), &flameSourceColorSecondary, 100 - (smoothHiliteGradient(i, COLS - 1) + 25));
! 233:
! 234: colors[i][(ROWS + MENU_FLAME_ROW_PADDING)-1] = &(colorStorage[colorSourceCount]);
! 235: colorSourceCount++;
! 236: }
! 237:
! 238: if (includeTitle) {
! 239: // Wreathe the title in flames, and mask it in black.
! 240: for (i=0; i<MENU_TITLE_WIDTH; i++) {
! 241: for (j=0; j<MENU_TITLE_HEIGHT; j++) {
! 242: if (title[j][i] != ' ') {
! 243: colors[(COLS - MENU_TITLE_WIDTH)/2 + i + MENU_TITLE_OFFSET_X][(ROWS - MENU_TITLE_HEIGHT)/2 + j + MENU_TITLE_OFFSET_Y] = &flameTitleColor;
! 244: colorSourceCount++;
! 245: mask[(COLS - MENU_TITLE_WIDTH)/2 + i + MENU_TITLE_OFFSET_X][(ROWS - MENU_TITLE_HEIGHT)/2 + j + MENU_TITLE_OFFSET_Y] = 100;
! 246: }
! 247: }
! 248: }
! 249:
! 250: // Anti-alias the mask.
! 251: antiAlias(mask);
! 252: }
! 253:
! 254: brogueAssert(colorSourceCount <= MENU_FLAME_COLOR_SOURCE_COUNT);
! 255:
! 256: // Simulate the background flames for a while
! 257: for (i=0; i<100; i++) {
! 258: updateMenuFlames(colors, colorSources, flames);
! 259: }
! 260:
! 261: }
! 262:
! 263: void titleMenu() {
! 264: signed short flames[COLS][(ROWS + MENU_FLAME_ROW_PADDING)][3]; // red, green and blue
! 265: signed short colorSources[MENU_FLAME_COLOR_SOURCE_COUNT][4]; // red, green, blue, and rand, one for each color source (no more than MENU_FLAME_COLOR_SOURCE_COUNT).
! 266: const color *colors[COLS][(ROWS + MENU_FLAME_ROW_PADDING)];
! 267: color colorStorage[COLS];
! 268: unsigned char mask[COLS][ROWS];
! 269: boolean controlKeyWasDown = false;
! 270:
! 271: short i, b, x, y, button;
! 272: buttonState state;
! 273: brogueButton buttons[6];
! 274: char whiteColorEscape[10] = "";
! 275: char goldColorEscape[10] = "";
! 276: char newGameText[100] = "", customNewGameText[100] = "";
! 277: rogueEvent theEvent;
! 278: enum NGCommands buttonCommands[6] = {NG_NEW_GAME, NG_OPEN_GAME, NG_VIEW_RECORDING, NG_HIGH_SCORES, NG_QUIT};
! 279:
! 280: cellDisplayBuffer shadowBuf[COLS][ROWS];
! 281:
! 282: // Initialize the RNG so the flames aren't always the same.
! 283:
! 284: seedRandomGenerator(0);
! 285:
! 286: // Empty nextGamePath and nextGameSeed so that the buttons don't try to load an old game path or seed.
! 287: rogue.nextGamePath[0] = '\0';
! 288: rogue.nextGameSeed = 0;
! 289:
! 290: // Initialize the title menu buttons.
! 291: encodeMessageColor(whiteColorEscape, 0, &white);
! 292: encodeMessageColor(goldColorEscape, 0, KEYBOARD_LABELS ? &itemMessageColor : &white);
! 293: sprintf(newGameText, " %sN%sew Game ", goldColorEscape, whiteColorEscape);
! 294: sprintf(customNewGameText, " %sN%sew Game (custom) ", goldColorEscape, whiteColorEscape);
! 295: b = 0;
! 296: button = -1;
! 297:
! 298: initializeButton(&(buttons[b]));
! 299: strcpy(buttons[b].text, newGameText);
! 300: buttons[b].hotkey[0] = 'n';
! 301: buttons[b].hotkey[1] = 'N';
! 302: b++;
! 303:
! 304: initializeButton(&(buttons[b]));
! 305: sprintf(buttons[b].text, " %sO%spen Game ", goldColorEscape, whiteColorEscape);
! 306: buttons[b].hotkey[0] = 'o';
! 307: buttons[b].hotkey[1] = 'O';
! 308: b++;
! 309:
! 310: initializeButton(&(buttons[b]));
! 311: sprintf(buttons[b].text, " %sV%siew Recording ", goldColorEscape, whiteColorEscape);
! 312: buttons[b].hotkey[0] = 'v';
! 313: buttons[b].hotkey[1] = 'V';
! 314: b++;
! 315:
! 316: initializeButton(&(buttons[b]));
! 317: sprintf(buttons[b].text, " %sH%sigh Scores ", goldColorEscape, whiteColorEscape);
! 318: buttons[b].hotkey[0] = 'h';
! 319: buttons[b].hotkey[1] = 'H';
! 320: b++;
! 321:
! 322: initializeButton(&(buttons[b]));
! 323: sprintf(buttons[b].text, " %sQ%suit ", goldColorEscape, whiteColorEscape);
! 324: buttons[b].hotkey[0] = 'q';
! 325: buttons[b].hotkey[1] = 'Q';
! 326: b++;
! 327:
! 328: x = COLS - 1 - 20 - 2;
! 329: y = ROWS - 1;
! 330: for (i = b-1; i >= 0; i--) {
! 331: y -= 2;
! 332: buttons[i].x = x;
! 333: buttons[i].y = y;
! 334: buttons[i].buttonColor = titleButtonColor;
! 335: buttons[i].flags |= B_WIDE_CLICK_AREA;
! 336: }
! 337:
! 338: blackOutScreen();
! 339: clearDisplayBuffer(shadowBuf);
! 340: initializeButtonState(&state, buttons, b, x, y, 20, b*2-1);
! 341: rectangularShading(x, y, 20, b*2-1, &black, INTERFACE_OPACITY, shadowBuf);
! 342: drawButtonsInState(&state);
! 343:
! 344: initializeMenuFlames(true, colors, colorStorage, colorSources, flames, mask);
! 345: rogue.creaturesWillFlashThisTurn = false; // total unconscionable hack
! 346:
! 347: do {
! 348: if (isApplicationActive()) {
! 349: // Revert the display.
! 350: overlayDisplayBuffer(state.rbuf, NULL);
! 351:
! 352: if (!controlKeyWasDown && controlKeyIsDown()) {
! 353: strcpy(state.buttons[0].text, customNewGameText);
! 354: drawButtonsInState(&state);
! 355: buttonCommands[0] = NG_NEW_GAME_WITH_SEED;
! 356: controlKeyWasDown = true;
! 357: } else if (controlKeyWasDown && !controlKeyIsDown()) {
! 358: strcpy(state.buttons[0].text, newGameText);
! 359: drawButtonsInState(&state);
! 360: buttonCommands[0] = NG_NEW_GAME;
! 361: controlKeyWasDown = false;
! 362: }
! 363:
! 364: // Update the display.
! 365: updateMenuFlames(colors, colorSources, flames);
! 366: drawMenuFlames(flames, mask);
! 367: overlayDisplayBuffer(shadowBuf, NULL);
! 368: overlayDisplayBuffer(state.dbuf, NULL);
! 369:
! 370: // Pause briefly.
! 371: if (pauseBrogue(MENU_FLAME_UPDATE_DELAY)) {
! 372: // There was input during the pause! Get the input.
! 373: nextBrogueEvent(&theEvent, true, false, true);
! 374:
! 375: // Process the input.
! 376: button = processButtonInput(&state, NULL, &theEvent);
! 377: }
! 378:
! 379: } else {
! 380: pauseBrogue(64);
! 381: }
! 382: } while (button == -1 && rogue.nextGame == NG_NOTHING);
! 383: drawMenuFlames(flames, mask);
! 384: if (button != -1) {
! 385: if (button == 0 && controlKeyIsDown()) {
! 386: // Should fix an issue with Linux/Windows ports that require moving the mouse after
! 387: // pressing control to get the button to change.
! 388: rogue.nextGame = NG_NEW_GAME_WITH_SEED;
! 389: } else {
! 390: rogue.nextGame = buttonCommands[button];
! 391: }
! 392: }
! 393: }
! 394:
! 395: // Closes Brogue without any further prompts, animations, or user interaction.
! 396: void quitImmediately() {
! 397: // If we are recording a game, save it.
! 398: if (rogue.recording) {
! 399: flushBufferToFile();
! 400: if (rogue.gameInProgress && !rogue.quit && !rogue.gameHasEnded) {
! 401: // Game isn't over yet, create a savegame.
! 402: saveGameNoPrompt();
! 403: } else {
! 404: // Save it as a recording.
! 405: char path[BROGUE_FILENAME_MAX];
! 406: saveRecordingNoPrompt(path);
! 407: }
! 408: }
! 409: exit(0);
! 410: }
! 411:
! 412: void dialogAlert(char *message) {
! 413: cellDisplayBuffer rbuf[COLS][ROWS];
! 414:
! 415: brogueButton OKButton;
! 416: initializeButton(&OKButton);
! 417: strcpy(OKButton.text, " OK ");
! 418: OKButton.hotkey[0] = RETURN_KEY;
! 419: OKButton.hotkey[1] = ACKNOWLEDGE_KEY;
! 420: printTextBox(message, COLS/3, ROWS/3, COLS/3, &white, &interfaceBoxColor, rbuf, &OKButton, 1);
! 421: overlayDisplayBuffer(rbuf, NULL);
! 422: }
! 423:
! 424: boolean stringsExactlyMatch(const char *string1, const char *string2) {
! 425: short i;
! 426: for (i=0; string1[i] && string2[i]; i++) {
! 427: if (string1[i] != string2[i]) {
! 428: return false;
! 429: }
! 430: }
! 431: return string1[i] == string2[i];
! 432: }
! 433:
! 434: // Used to compare the dates of two fileEntry variables
! 435: // Returns (int):
! 436: // < 0 if 'b' date is lesser than 'a' date
! 437: // = 0 if 'b' date is equal to 'a' date,
! 438: // > 0 if 'b' date is greater than 'a' date
! 439: int fileEntryCompareDates(const void *a, const void *b) {
! 440: fileEntry *f1 = (fileEntry *)a;
! 441: fileEntry *f2 = (fileEntry *)b;
! 442: time_t t1, t2;
! 443: double diff;
! 444:
! 445: t1 = mktime(&f1->date);
! 446: t2 = mktime(&f2->date);
! 447: diff = difftime(t2, t1);
! 448:
! 449: //char date_f1[11];
! 450: //char date_f2[11];
! 451: //strftime(date_f1, sizeof(date_f1), DATE_FORMAT, &f1->date);
! 452: //strftime(date_f2, sizeof(date_f2), DATE_FORMAT, &f2->date);
! 453: //printf("\nf1: %s\t%s",date_f1,f1->path);
! 454: //printf("\nf2: %s\t%s",date_f2,f2->path);
! 455: //printf("\ndiff: %f\n", diff);
! 456:
! 457: return (int)diff;
! 458: }
! 459:
! 460: #define FILES_ON_PAGE_MAX (min(26, ROWS - 7)) // Two rows (top and bottom) for flames, two rows for border, one for prompt, one for heading.
! 461: #define MAX_FILENAME_DISPLAY_LENGTH 53
! 462: boolean dialogChooseFile(char *path, const char *suffix, const char *prompt) {
! 463: short i, j, count, x, y, width, height, suffixLength, pathLength, maxPathLength, currentPageStart;
! 464: brogueButton buttons[FILES_ON_PAGE_MAX + 2];
! 465: fileEntry *files;
! 466: boolean retval = false, again;
! 467: cellDisplayBuffer dbuf[COLS][ROWS], rbuf[COLS][ROWS];
! 468: color *dialogColor = &interfaceBoxColor;
! 469: char *membuf;
! 470: char fileDate [11];
! 471:
! 472: suffixLength = strlen(suffix);
! 473: files = listFiles(&count, &membuf);
! 474: copyDisplayBuffer(rbuf, displayBuffer);
! 475: maxPathLength = strLenWithoutEscapes(prompt);
! 476:
! 477: // First, we want to filter the list by stripping out any filenames that do not end with suffix.
! 478: // i is the entry we're testing, and j is the entry that we move it to if it qualifies.
! 479: for (i=0, j=0; i<count; i++) {
! 480: pathLength = strlen(files[i].path);
! 481: //printf("\nString 1: %s", &(files[i].path[(max(0, pathLength - suffixLength))]));
! 482: if (stringsExactlyMatch(&(files[i].path[(max(0, pathLength - suffixLength))]), suffix)) {
! 483:
! 484: // This file counts!
! 485: if (i > j) {
! 486: files[j] = files[i];
! 487: //strftime(fileDate, sizeof(fileDate), DATE_FORMAT, &files[j].date);
! 488: //printf("\nMatching file: %s\twith date: %s", files[j].path, fileDate);
! 489: }
! 490: j++;
! 491:
! 492: // Keep track of the longest length.
! 493: if (min(pathLength, MAX_FILENAME_DISPLAY_LENGTH) + 10 > maxPathLength) {
! 494: maxPathLength = min(pathLength, MAX_FILENAME_DISPLAY_LENGTH) + 10;
! 495: }
! 496: }
! 497: }
! 498: count = j;
! 499:
! 500: // Once we have all relevant files, we sort them by date descending
! 501: qsort(files, count, sizeof(fileEntry), &fileEntryCompareDates);
! 502:
! 503: currentPageStart = 0;
! 504:
! 505: do { // Repeat to permit scrolling.
! 506: again = false;
! 507:
! 508: for (i=0; i<min(count - currentPageStart, FILES_ON_PAGE_MAX); i++) {
! 509: initializeButton(&(buttons[i]));
! 510: buttons[i].flags &= ~(B_WIDE_CLICK_AREA | B_GRADIENT);
! 511: buttons[i].buttonColor = *dialogColor;
! 512: if (KEYBOARD_LABELS) {
! 513: sprintf(buttons[i].text, "%c) ", 'a' + i);
! 514: } else {
! 515: buttons[i].text[0] = '\0';
! 516: }
! 517: strncat(buttons[i].text, files[currentPageStart+i].path, MAX_FILENAME_DISPLAY_LENGTH);
! 518:
! 519: // Clip off the file suffix from the button text.
! 520: buttons[i].text[strlen(buttons[i].text) - suffixLength] = '\0'; // Snip!
! 521: buttons[i].hotkey[0] = 'a' + i;
! 522: buttons[i].hotkey[1] = 'A' + i;
! 523:
! 524: // Clip the filename length if necessary.
! 525: if (strlen(buttons[i].text) > MAX_FILENAME_DISPLAY_LENGTH) {
! 526: strcpy(&(buttons[i].text[MAX_FILENAME_DISPLAY_LENGTH - 3]), "...");
! 527: }
! 528:
! 529: //strftime(fileDate, sizeof(fileDate), DATE_FORMAT, &files[currentPageStart+i].date);
! 530: //printf("\nFound file: %s, with date: %s", files[currentPageStart+i].path, fileDate);
! 531: }
! 532:
! 533: x = (COLS - maxPathLength) / 2;
! 534: width = maxPathLength;
! 535: height = min(count - currentPageStart, FILES_ON_PAGE_MAX) + 2;
! 536: y = max(4, (ROWS - height) / 2);
! 537:
! 538: for (i=0; i<min(count - currentPageStart, FILES_ON_PAGE_MAX); i++) {
! 539: pathLength = strlen(buttons[i].text);
! 540: for (j=pathLength; j<(width - 10); j++) {
! 541: buttons[i].text[j] = ' ';
! 542: }
! 543: buttons[i].text[j] = '\0';
! 544: strftime(fileDate, sizeof(fileDate), DATE_FORMAT, &files[currentPageStart+i].date);
! 545: strcpy(&(buttons[i].text[j]), fileDate);
! 546: buttons[i].x = x;
! 547: buttons[i].y = y + 1 + i;
! 548: }
! 549:
! 550: if (count > FILES_ON_PAGE_MAX) {
! 551: // Create up and down arrows.
! 552: initializeButton(&(buttons[i]));
! 553: strcpy(buttons[i].text, " * ");
! 554: buttons[i].symbol[0] = G_UP_ARROW;
! 555: if (currentPageStart <= 0) {
! 556: buttons[i].flags &= ~(B_ENABLED | B_DRAW);
! 557: } else {
! 558: buttons[i].hotkey[0] = UP_ARROW;
! 559: buttons[i].hotkey[1] = NUMPAD_8;
! 560: buttons[i].hotkey[2] = PAGE_UP_KEY;
! 561: }
! 562: buttons[i].x = x + (width - 11)/2;
! 563: buttons[i].y = y;
! 564:
! 565: i++;
! 566: initializeButton(&(buttons[i]));
! 567: strcpy(buttons[i].text, " * ");
! 568: buttons[i].symbol[0] = G_DOWN_ARROW;
! 569: if (currentPageStart + FILES_ON_PAGE_MAX >= count) {
! 570: buttons[i].flags &= ~(B_ENABLED | B_DRAW);
! 571: } else {
! 572: buttons[i].hotkey[0] = DOWN_ARROW;
! 573: buttons[i].hotkey[1] = NUMPAD_2;
! 574: buttons[i].hotkey[2] = PAGE_DOWN_KEY;
! 575: }
! 576: buttons[i].x = x + (width - 11)/2;
! 577: buttons[i].y = y + i;
! 578: }
! 579:
! 580: if (count) {
! 581: clearDisplayBuffer(dbuf);
! 582: printString(prompt, x, y - 1, &itemMessageColor, dialogColor, dbuf);
! 583: rectangularShading(x - 1, y - 1, width + 1, height + 1, dialogColor, INTERFACE_OPACITY, dbuf);
! 584: overlayDisplayBuffer(dbuf, NULL);
! 585:
! 586: // for (j=0; j<min(count - currentPageStart, FILES_ON_PAGE_MAX); j++) {
! 587: // strftime(fileDate, sizeof(fileDate), DATE_FORMAT, &files[currentPageStart+j].date);
! 588: // printf("\nSanity check BEFORE: %s, with date: %s", files[currentPageStart+j].path, fileDate);
! 589: // printf("\n (button name)Sanity check BEFORE: %s", buttons[j].text);
! 590: // }
! 591:
! 592: i = buttonInputLoop(buttons,
! 593: min(count - currentPageStart, FILES_ON_PAGE_MAX) + (count > FILES_ON_PAGE_MAX ? 2 : 0),
! 594: x,
! 595: y,
! 596: width,
! 597: height,
! 598: NULL);
! 599:
! 600: // for (j=0; j<min(count - currentPageStart, FILES_ON_PAGE_MAX); j++) {
! 601: // strftime(fileDate, sizeof(fileDate), DATE_FORMAT, &files[currentPageStart+j].date);
! 602: // printf("\nSanity check AFTER: %s, with date: %s", files[currentPageStart+j].path, fileDate);
! 603: // printf("\n (button name)Sanity check AFTER: %s", buttons[j].text);
! 604: // }
! 605:
! 606: overlayDisplayBuffer(rbuf, NULL);
! 607:
! 608: if (i < min(count - currentPageStart, FILES_ON_PAGE_MAX)) {
! 609: if (i >= 0) {
! 610: retval = true;
! 611: strcpy(path, files[currentPageStart+i].path);
! 612: } else { // i is -1
! 613: retval = false;
! 614: }
! 615: } else if (i == min(count - currentPageStart, FILES_ON_PAGE_MAX)) { // Up arrow
! 616: again = true;
! 617: currentPageStart -= FILES_ON_PAGE_MAX;
! 618: } else if (i == min(count - currentPageStart, FILES_ON_PAGE_MAX) + 1) { // Down arrow
! 619: again = true;
! 620: currentPageStart += FILES_ON_PAGE_MAX;
! 621: }
! 622: }
! 623:
! 624: } while (again);
! 625:
! 626: free(files);
! 627: free(membuf);
! 628:
! 629: if (count == 0) {
! 630: dialogAlert("No applicable files found.");
! 631: return false;
! 632: } else {
! 633: return retval;
! 634: }
! 635: }
! 636:
! 637: // This is the basic program loop.
! 638: // When the program launches, or when a game ends, you end up here.
! 639: // If the player has already said what he wants to do next
! 640: // (by storing it in rogue.nextGame -- possibilities listed in enum NGCommands),
! 641: // we'll do it. The path (rogue.nextGamePath) is essentially a parameter for this command, and
! 642: // tells NG_VIEW_RECORDING and NG_OPEN_GAME which file to open. If there is a command but no
! 643: // accompanying path, and it's a command that should take a path, then pop up a dialog to have
! 644: // the player specify a path. If there is no command (i.e. if rogue.nextGame contains NG_NOTHING),
! 645: // then we'll display the title screen so the player can choose.
! 646: void mainBrogueJunction() {
! 647: rogueEvent theEvent;
! 648: char path[BROGUE_FILENAME_MAX], buf[100], seedDefault[100];
! 649: char *maxSeed = "4294967295"; // 2^32 - 1
! 650: short i, j, k;
! 651: boolean seedTooBig;
! 652:
! 653: // clear screen and display buffer
! 654: for (i=0; i<COLS; i++) {
! 655: for (j=0; j<ROWS; j++) {
! 656: displayBuffer[i][j].character = 0;
! 657: displayBuffer[i][j].needsUpdate = false;
! 658: displayBuffer[i][j].opacity = 100;
! 659: for (k=0; k<3; k++) {
! 660: displayBuffer[i][j].foreColorComponents[k] = 0;
! 661: displayBuffer[i][j].backColorComponents[k] = 0;
! 662: }
! 663: plotCharWithColor(' ', i, j, &black, &black);
! 664: }
! 665: }
! 666:
! 667: initializeLaunchArguments(&rogue.nextGame, rogue.nextGamePath, &rogue.nextGameSeed);
! 668:
! 669: do {
! 670: rogue.gameHasEnded = false;
! 671: rogue.playbackFastForward = false;
! 672: rogue.playbackMode = false;
! 673: switch (rogue.nextGame) {
! 674: case NG_NOTHING:
! 675: // Run the main menu to get a decision out of the player.
! 676: titleMenu();
! 677: break;
! 678: case NG_NEW_GAME:
! 679: case NG_NEW_GAME_WITH_SEED:
! 680: rogue.nextGamePath[0] = '\0';
! 681: randomNumbersGenerated = 0;
! 682:
! 683: rogue.playbackMode = false;
! 684: rogue.playbackFastForward = false;
! 685: rogue.playbackBetweenTurns = false;
! 686:
! 687: getAvailableFilePath(path, LAST_GAME_NAME, GAME_SUFFIX);
! 688: strcat(path, GAME_SUFFIX);
! 689: strcpy(currentFilePath, path);
! 690:
! 691: if (rogue.nextGame == NG_NEW_GAME_WITH_SEED) {
! 692: if (rogue.nextGameSeed == 0) { // Prompt for seed; default is the previous game's seed.
! 693: if (previousGameSeed == 0) {
! 694: seedDefault[0] = '\0';
! 695: } else {
! 696: sprintf(seedDefault, "%lu", previousGameSeed);
! 697: }
! 698: if (getInputTextString(buf, "Generate dungeon with seed number:",
! 699: strlen(maxSeed),
! 700: seedDefault,
! 701: "",
! 702: TEXT_INPUT_NUMBERS,
! 703: true)
! 704: && buf[0] != '\0') {
! 705: seedTooBig = false;
! 706: if (strlen(buf) == strlen(maxSeed)) {
! 707: for (i=0; maxSeed[i]; i++) {
! 708: if (maxSeed[i] > buf[i]) {
! 709: break; // we're good
! 710: } else if (maxSeed[i] < buf[i]) {
! 711: seedTooBig = true;
! 712: break;
! 713: }
! 714: }
! 715: }
! 716: sscanf(seedTooBig ? maxSeed : buf, "%lu", &rogue.nextGameSeed);
! 717: } else {
! 718: rogue.nextGame = NG_NOTHING;
! 719: break; // Don't start a new game after all.
! 720: }
! 721: }
! 722: } else {
! 723: rogue.nextGameSeed = 0; // Seed based on clock.
! 724: }
! 725:
! 726: rogue.nextGame = NG_NOTHING;
! 727: initializeRogue(rogue.nextGameSeed);
! 728: startLevel(rogue.depthLevel, 1); // descending into level 1
! 729:
! 730: mainInputLoop();
! 731: if(serverMode) {
! 732: rogue.nextGame = NG_QUIT;
! 733: }
! 734: freeEverything();
! 735: break;
! 736: case NG_OPEN_GAME:
! 737: rogue.nextGame = NG_NOTHING;
! 738: path[0] = '\0';
! 739: if (rogue.nextGamePath[0]) {
! 740: strcpy(path, rogue.nextGamePath);
! 741: rogue.nextGamePath[0] = '\0';
! 742: } else {
! 743: dialogChooseFile(path, GAME_SUFFIX, "Open saved game:");
! 744: //chooseFile(path, "Open saved game: ", "Saved game", GAME_SUFFIX);
! 745: }
! 746:
! 747: if (openFile(path)) {
! 748: if (loadSavedGame()) {
! 749: mainInputLoop();
! 750: }
! 751: freeEverything();
! 752: } else {
! 753: //dialogAlert("File not found.");
! 754: }
! 755: rogue.playbackMode = false;
! 756: rogue.playbackOOS = false;
! 757:
! 758: if(serverMode) {
! 759: rogue.nextGame = NG_QUIT;
! 760: }
! 761: break;
! 762: case NG_VIEW_RECORDING:
! 763: rogue.nextGame = NG_NOTHING;
! 764:
! 765: path[0] = '\0';
! 766: if (rogue.nextGamePath[0]) {
! 767: strcpy(path, rogue.nextGamePath);
! 768: rogue.nextGamePath[0] = '\0';
! 769: } else {
! 770: dialogChooseFile(path, RECORDING_SUFFIX, "View recording:");
! 771: //chooseFile(path, "View recording: ", "Recording", RECORDING_SUFFIX);
! 772: }
! 773:
! 774: if (openFile(path)) {
! 775: randomNumbersGenerated = 0;
! 776: rogue.playbackMode = true;
! 777: initializeRogue(0); // Seed argument is ignored because we're in playback.
! 778: if (!rogue.gameHasEnded) {
! 779: startLevel(rogue.depthLevel, 1);
! 780: rogue.playbackPaused = true;
! 781: displayAnnotation(); // in case there's an annotation for turn 0
! 782: }
! 783:
! 784: while(!rogue.gameHasEnded && rogue.playbackMode) {
! 785: if (rogue.playbackPaused) {
! 786: rogue.playbackPaused = false;
! 787: pausePlayback();
! 788: }
! 789: #ifdef ENABLE_PLAYBACK_SWITCH
! 790: // We are coming from the end of a recording the user has taken over.
! 791: // No more event checks, that has already been handled
! 792: if (rogue.gameHasEnded) {
! 793: break;
! 794: }
! 795: #endif
! 796: rogue.RNG = RNG_COSMETIC; // dancing terrain colors can't influence recordings
! 797: rogue.playbackBetweenTurns = true;
! 798: nextBrogueEvent(&theEvent, false, true, false);
! 799: rogue.RNG = RNG_SUBSTANTIVE;
! 800:
! 801: executeEvent(&theEvent);
! 802: }
! 803:
! 804: freeEverything();
! 805: } else {
! 806: // announce file not found
! 807: }
! 808: rogue.playbackMode = false;
! 809: rogue.playbackOOS = false;
! 810:
! 811: if(serverMode) {
! 812: rogue.nextGame = NG_QUIT;
! 813: }
! 814: break;
! 815: case NG_HIGH_SCORES:
! 816: rogue.nextGame = NG_NOTHING;
! 817: printHighScores(false);
! 818: break;
! 819: case NG_QUIT:
! 820: // No need to do anything.
! 821: break;
! 822: default:
! 823: break;
! 824: }
! 825: } while (rogue.nextGame != NG_QUIT);
! 826: }
CVSweb