[BACK]Return to MainMenu.c CVS log [TXT][DIR] Up to [contributed] / brogue-ce / src / brogue

File: [contributed] / brogue-ce / src / brogue / MainMenu.c (download)

Revision 1.1, Thu May 27 20:31:42 2021 UTC (2 years, 11 months ago) by rubenllorente
Branch point for: MAIN

Initial revision

/*
 *  Buttons.c
 *  Brogue
 *
 *  Created by Brian Walker on 1/14/12.
 *  Copyright 2012. All rights reserved.
 *
 *  This file is part of Brogue.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "Rogue.h"
#include "IncludeGlobals.h"
#include <time.h>
#include <limits.h>

#define MENU_FLAME_PRECISION_FACTOR     10
#define MENU_FLAME_RISE_SPEED           50
#define MENU_FLAME_SPREAD_SPEED         20
#define MENU_FLAME_COLOR_DRIFT_SPEED    500
#define MENU_FLAME_FADE_SPEED           20
#define MENU_FLAME_UPDATE_DELAY         50
#define MENU_FLAME_ROW_PADDING          2
#define MENU_TITLE_OFFSET_X             (-4)
#define MENU_TITLE_OFFSET_Y             (-1)

#define MENU_FLAME_COLOR_SOURCE_COUNT   1136

#define MENU_FLAME_DENOMINATOR          (100 + MENU_FLAME_RISE_SPEED + MENU_FLAME_SPREAD_SPEED)


void drawMenuFlames(signed short flames[COLS][(ROWS + MENU_FLAME_ROW_PADDING)][3], unsigned char mask[COLS][ROWS]) {
    short i, j, versionStringLength;
    color tempColor = {0};
    const color *maskColor = &black;
    char dchar;

    versionStringLength = strLenWithoutEscapes(BROGUE_VERSION_STRING);

    for (j=0; j<ROWS; j++) {
        for (i=0; i<COLS; i++) {
            if (j == ROWS - 1 && i >= COLS - versionStringLength) {
                dchar = BROGUE_VERSION_STRING[i - (COLS - versionStringLength)];
            } else {
                dchar = ' ';
            }

            if (mask[i][j] == 100) {
                plotCharWithColor(dchar, i, j, &veryDarkGray, maskColor);
            } else {
                tempColor = black;
                tempColor.red   = flames[i][j][0] / MENU_FLAME_PRECISION_FACTOR;
                tempColor.green = flames[i][j][1] / MENU_FLAME_PRECISION_FACTOR;
                tempColor.blue  = flames[i][j][2] / MENU_FLAME_PRECISION_FACTOR;
                if (mask[i][j] > 0) {
                    applyColorAverage(&tempColor, maskColor, mask[i][j]);
                }
                plotCharWithColor(dchar, i, j, &veryDarkGray, &tempColor);
            }
        }
    }
}

void updateMenuFlames(const color *colors[COLS][(ROWS + MENU_FLAME_ROW_PADDING)],
                      signed short colorSources[MENU_FLAME_COLOR_SOURCE_COUNT][4],
                      signed short flames[COLS][(ROWS + MENU_FLAME_ROW_PADDING)][3]) {

    short i, j, k, l, x, y;
    signed short tempFlames[COLS][3];
    short colorSourceNumber, rand;

    colorSourceNumber = 0;
    for (j=0; j<(ROWS + MENU_FLAME_ROW_PADDING); j++) {
        // Make a temp copy of the current row.
        for (i=0; i<COLS; i++) {
            for (k=0; k<3; k++) {
                tempFlames[i][k] = flames[i][j][k];
            }
        }

        for (i=0; i<COLS; i++) {
            // Each cell is the weighted average of the three color values below and itself.
            // Weight of itself: 100
            // Weight of left and right neighbors: MENU_FLAME_SPREAD_SPEED / 2 each
            // Weight of below cell: MENU_FLAME_RISE_SPEED
            // Divisor: 100 + MENU_FLAME_SPREAD_SPEED + MENU_FLAME_RISE_SPEED

            // Itself:
            for (k=0; k<3; k++) {
                flames[i][j][k] = 100 * flames[i][j][k] / MENU_FLAME_DENOMINATOR;
            }

            // Left and right neighbors:
            for (l = -1; l <= 1; l += 2) {
                x = i + l;
                if (x == -1) {
                    x = COLS - 1;
                } else if (x == COLS) {
                    x = 0;
                }
                for (k=0; k<3; k++) {
                    flames[i][j][k] += MENU_FLAME_SPREAD_SPEED * tempFlames[x][k] / 2 / MENU_FLAME_DENOMINATOR;
                }
            }

            // Below:
            y = j + 1;
            if (y < (ROWS + MENU_FLAME_ROW_PADDING)) {
                for (k=0; k<3; k++) {
                    flames[i][j][k] += MENU_FLAME_RISE_SPEED * flames[i][y][k] / MENU_FLAME_DENOMINATOR;
                }
            }

            // Fade a little:
            for (k=0; k<3; k++) {
                flames[i][j][k] = (1000 - MENU_FLAME_FADE_SPEED) * flames[i][j][k] / 1000;
            }

            if (colors[i][j]) {
                // If it's a color source tile:

                // First, cause the color to drift a little.
                for (k=0; k<4; k++) {
                    colorSources[colorSourceNumber][k] += rand_range(-MENU_FLAME_COLOR_DRIFT_SPEED, MENU_FLAME_COLOR_DRIFT_SPEED);
                    colorSources[colorSourceNumber][k] = clamp(colorSources[colorSourceNumber][k], 0, 1000);
                }

                // Then, add the color to this tile's flames.
                rand = colors[i][j]->rand * colorSources[colorSourceNumber][0] / 1000;
                flames[i][j][0] += (colors[i][j]->red   + (colors[i][j]->redRand    * colorSources[colorSourceNumber][1] / 1000) + rand) * MENU_FLAME_PRECISION_FACTOR;
                flames[i][j][1] += (colors[i][j]->green + (colors[i][j]->greenRand  * colorSources[colorSourceNumber][2] / 1000) + rand) * MENU_FLAME_PRECISION_FACTOR;
                flames[i][j][2] += (colors[i][j]->blue  + (colors[i][j]->blueRand   * colorSources[colorSourceNumber][3] / 1000) + rand) * MENU_FLAME_PRECISION_FACTOR;

                colorSourceNumber++;
            }
        }
    }
}

// Takes a grid of values, each of which is 0 or 100, and fills in some middle values in the interstices.
void antiAlias(unsigned char mask[COLS][ROWS]) {
    short i, j, x, y, dir, nbCount;
    const short intensity[5] = {0, 0, 35, 50, 60};

    for (i=0; i<COLS; i++) {
        for (j=0; j<ROWS; j++) {
            if (mask[i][j] < 100) {
                nbCount = 0;
                for (dir=0; dir<4; dir++) {
                    x = i + nbDirs[dir][0];
                    y = j + nbDirs[dir][1];
                    if (coordinatesAreInWindow(x, y) && mask[x][y] == 100) {
                        nbCount++;
                    }
                }
                mask[i][j] = intensity[nbCount];
            }
        }
    }
}

#define MENU_TITLE_WIDTH    74
#define MENU_TITLE_HEIGHT   19

void initializeMenuFlames(boolean includeTitle,
                          const color *colors[COLS][(ROWS + MENU_FLAME_ROW_PADDING)],
                          color colorStorage[COLS],
                          signed short colorSources[MENU_FLAME_COLOR_SOURCE_COUNT][4],
                          signed short flames[COLS][(ROWS + MENU_FLAME_ROW_PADDING)][3],
                          unsigned char mask[COLS][ROWS]) {
    short i, j, k, colorSourceCount;
    const char title[MENU_TITLE_HEIGHT][MENU_TITLE_WIDTH+1] = {
        "########   ########       ######         #######   ####     ###  #########",
        " ##   ###   ##   ###    ##     ###     ##      ##   ##       #    ##     #",
        " ##    ##   ##    ##   ##       ###   ##        #   ##       #    ##     #",
        " ##    ##   ##    ##   #    #    ##   #         #   ##       #    ##      ",
        " ##    ##   ##    ##  ##   ##     ## ##             ##       #    ##    # ",
        " ##   ##    ##   ##   ##   ###    ## ##             ##       #    ##    # ",
        " ######     ## ###    ##   ####   ## ##             ##       #    ####### ",
        " ##    ##   ##  ##    ##   ####   ## ##             ##       #    ##    # ",
        " ##     ##  ##   ##   ##    ###   ## ##      #####  ##       #    ##    # ",
        " ##     ##  ##   ##   ###    ##   ## ###       ##   ##       #    ##      ",
        " ##     ##  ##    ##   ##    #    #   ##       ##   ##       #    ##      ",
        " ##     ##  ##    ##   ###       ##   ###      ##   ###      #    ##     #",
        " ##    ##   ##     ##   ###     ##     ###    ###    ###    #     ##     #",
        "########   ####    ###    ######         #####        ######     #########",
        "                            ##                                            ",
        "                        ##########                                        ",
        "                            ##                                            ",
        "                            ##                                            ",
        "                           ####                                           ",
    };

    for (i=0; i<COLS; i++) {
        for (j=0; j<ROWS; j++) {
            mask[i][j] = 0;
        }
    }

    for (i=0; i<COLS; i++) {
        for (j=0; j<(ROWS + MENU_FLAME_ROW_PADDING); j++) {
            colors[i][j] = NULL;
            for (k=0; k<3; k++) {
                flames[i][j][k] = 0;
            }
        }
    }

    // Seed source color random components.
    for (i=0; i<MENU_FLAME_COLOR_SOURCE_COUNT; i++) {
        for (k=0; k<4; k++) {
            colorSources[i][k] = rand_range(0, 1000);
        }
    }

    // Put some flame source along the bottom row.
    colorSourceCount = 0;
    for (i=0; i<COLS; i++) {
        colorStorage[colorSourceCount] = flameSourceColor;
        applyColorAverage(&(colorStorage[colorSourceCount]), &flameSourceColorSecondary, 100 - (smoothHiliteGradient(i, COLS - 1) + 25));

        colors[i][(ROWS + MENU_FLAME_ROW_PADDING)-1] = &(colorStorage[colorSourceCount]);
        colorSourceCount++;
    }

    if (includeTitle) {
        // Wreathe the title in flames, and mask it in black.
        for (i=0; i<MENU_TITLE_WIDTH; i++) {
            for (j=0; j<MENU_TITLE_HEIGHT; j++) {
                if (title[j][i] != ' ') {
                    colors[(COLS - MENU_TITLE_WIDTH)/2 + i + MENU_TITLE_OFFSET_X][(ROWS - MENU_TITLE_HEIGHT)/2 + j + MENU_TITLE_OFFSET_Y] = &flameTitleColor;
                    colorSourceCount++;
                    mask[(COLS - MENU_TITLE_WIDTH)/2 + i + MENU_TITLE_OFFSET_X][(ROWS - MENU_TITLE_HEIGHT)/2 + j + MENU_TITLE_OFFSET_Y] = 100;
                }
            }
        }

        // Anti-alias the mask.
        antiAlias(mask);
    }

    brogueAssert(colorSourceCount <= MENU_FLAME_COLOR_SOURCE_COUNT);

    // Simulate the background flames for a while
    for (i=0; i<100; i++) {
        updateMenuFlames(colors, colorSources, flames);
    }

}

void titleMenu() {
    signed short flames[COLS][(ROWS + MENU_FLAME_ROW_PADDING)][3]; // red, green and blue
    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).
    const color *colors[COLS][(ROWS + MENU_FLAME_ROW_PADDING)];
    color colorStorage[COLS];
    unsigned char mask[COLS][ROWS];
    boolean controlKeyWasDown = false;

    short i, b, x, y, button;
    buttonState state;
    brogueButton buttons[6];
    char whiteColorEscape[10] = "";
    char goldColorEscape[10] = "";
    char newGameText[100] = "", customNewGameText[100] = "";
    rogueEvent theEvent;
    enum NGCommands buttonCommands[6] = {NG_NEW_GAME, NG_OPEN_GAME, NG_VIEW_RECORDING, NG_HIGH_SCORES, NG_QUIT};

    cellDisplayBuffer shadowBuf[COLS][ROWS];

    // Initialize the RNG so the flames aren't always the same.

    seedRandomGenerator(0);

    // Empty nextGamePath and nextGameSeed so that the buttons don't try to load an old game path or seed.
    rogue.nextGamePath[0] = '\0';
    rogue.nextGameSeed = 0;

    // Initialize the title menu buttons.
    encodeMessageColor(whiteColorEscape, 0, &white);
    encodeMessageColor(goldColorEscape, 0, KEYBOARD_LABELS ? &itemMessageColor : &white);
    sprintf(newGameText, "      %sN%sew Game      ", goldColorEscape, whiteColorEscape);
    sprintf(customNewGameText, " %sN%sew Game (custom) ", goldColorEscape, whiteColorEscape);
    b = 0;
    button = -1;

    initializeButton(&(buttons[b]));
    strcpy(buttons[b].text, newGameText);
    buttons[b].hotkey[0] = 'n';
    buttons[b].hotkey[1] = 'N';
    b++;

    initializeButton(&(buttons[b]));
    sprintf(buttons[b].text, "     %sO%spen Game      ", goldColorEscape, whiteColorEscape);
    buttons[b].hotkey[0] = 'o';
    buttons[b].hotkey[1] = 'O';
    b++;

    initializeButton(&(buttons[b]));
    sprintf(buttons[b].text, "   %sV%siew Recording   ", goldColorEscape, whiteColorEscape);
    buttons[b].hotkey[0] = 'v';
    buttons[b].hotkey[1] = 'V';
    b++;

    initializeButton(&(buttons[b]));
    sprintf(buttons[b].text, "    %sH%sigh Scores     ", goldColorEscape, whiteColorEscape);
    buttons[b].hotkey[0] = 'h';
    buttons[b].hotkey[1] = 'H';
    b++;

    initializeButton(&(buttons[b]));
    sprintf(buttons[b].text, "        %sQ%suit        ", goldColorEscape, whiteColorEscape);
    buttons[b].hotkey[0] = 'q';
    buttons[b].hotkey[1] = 'Q';
    b++;

    x = COLS - 1 - 20 - 2;
    y = ROWS - 1;
    for (i = b-1; i >= 0; i--) {
        y -= 2;
        buttons[i].x = x;
        buttons[i].y = y;
        buttons[i].buttonColor = titleButtonColor;
        buttons[i].flags |= B_WIDE_CLICK_AREA;
    }

    blackOutScreen();
    clearDisplayBuffer(shadowBuf);
    initializeButtonState(&state, buttons, b, x, y, 20, b*2-1);
    rectangularShading(x, y, 20, b*2-1, &black, INTERFACE_OPACITY, shadowBuf);
    drawButtonsInState(&state);

    initializeMenuFlames(true, colors, colorStorage, colorSources, flames, mask);
    rogue.creaturesWillFlashThisTurn = false; // total unconscionable hack

    do {
        if (isApplicationActive()) {
            // Revert the display.
            overlayDisplayBuffer(state.rbuf, NULL);

            if (!controlKeyWasDown && controlKeyIsDown()) {
                strcpy(state.buttons[0].text, customNewGameText);
                drawButtonsInState(&state);
                buttonCommands[0] = NG_NEW_GAME_WITH_SEED;
                controlKeyWasDown = true;
            } else if (controlKeyWasDown && !controlKeyIsDown()) {
                strcpy(state.buttons[0].text, newGameText);
                drawButtonsInState(&state);
                buttonCommands[0] = NG_NEW_GAME;
                controlKeyWasDown = false;
            }

            // Update the display.
            updateMenuFlames(colors, colorSources, flames);
            drawMenuFlames(flames, mask);
            overlayDisplayBuffer(shadowBuf, NULL);
            overlayDisplayBuffer(state.dbuf, NULL);

            // Pause briefly.
            if (pauseBrogue(MENU_FLAME_UPDATE_DELAY)) {
                // There was input during the pause! Get the input.
                nextBrogueEvent(&theEvent, true, false, true);

                // Process the input.
                button = processButtonInput(&state, NULL, &theEvent);
            }

        } else {
            pauseBrogue(64);
        }
    } while (button == -1 && rogue.nextGame == NG_NOTHING);
    drawMenuFlames(flames, mask);
    if (button != -1) {
        if (button == 0 && controlKeyIsDown()) {
            // Should fix an issue with Linux/Windows ports that require moving the mouse after
            // pressing control to get the button to change.
            rogue.nextGame = NG_NEW_GAME_WITH_SEED;
        } else {
            rogue.nextGame = buttonCommands[button];
        }
    }
}

// Closes Brogue without any further prompts, animations, or user interaction.
void quitImmediately() {
    // If we are recording a game, save it.
    if (rogue.recording) {
        flushBufferToFile();
        if (rogue.gameInProgress && !rogue.quit && !rogue.gameHasEnded) {
            // Game isn't over yet, create a savegame.
            saveGameNoPrompt();
        } else {
            // Save it as a recording.
            char path[BROGUE_FILENAME_MAX];
            saveRecordingNoPrompt(path);
        }
    }
    exit(0);
}

void dialogAlert(char *message) {
    cellDisplayBuffer rbuf[COLS][ROWS];

    brogueButton OKButton;
    initializeButton(&OKButton);
    strcpy(OKButton.text, "     OK     ");
    OKButton.hotkey[0] = RETURN_KEY;
    OKButton.hotkey[1] = ACKNOWLEDGE_KEY;
    printTextBox(message, COLS/3, ROWS/3, COLS/3, &white, &interfaceBoxColor, rbuf, &OKButton, 1);
    overlayDisplayBuffer(rbuf, NULL);
}

boolean stringsExactlyMatch(const char *string1, const char *string2) {
    short i;
    for (i=0; string1[i] && string2[i]; i++) {
        if (string1[i] != string2[i]) {
            return false;
        }
    }
    return string1[i] == string2[i];
}

// Used to compare the dates of two fileEntry variables
// Returns (int):
//      < 0 if 'b' date is lesser than 'a' date
//      = 0 if 'b' date is equal to 'a' date,
//      > 0 if 'b' date is greater than 'a' date
int fileEntryCompareDates(const void *a, const void *b) {
    fileEntry *f1 = (fileEntry *)a;
    fileEntry *f2 = (fileEntry *)b;
    time_t t1, t2;
    double diff;

    t1 = mktime(&f1->date);
    t2 = mktime(&f2->date);
    diff = difftime(t2, t1);

    //char date_f1[11];
    //char date_f2[11];
    //strftime(date_f1, sizeof(date_f1), DATE_FORMAT, &f1->date);
    //strftime(date_f2, sizeof(date_f2), DATE_FORMAT, &f2->date);
    //printf("\nf1: %s\t%s",date_f1,f1->path);
    //printf("\nf2: %s\t%s",date_f2,f2->path);
    //printf("\ndiff: %f\n", diff);

    return (int)diff;
}

#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.
#define MAX_FILENAME_DISPLAY_LENGTH     53
boolean dialogChooseFile(char *path, const char *suffix, const char *prompt) {
    short i, j, count, x, y, width, height, suffixLength, pathLength, maxPathLength, currentPageStart;
    brogueButton buttons[FILES_ON_PAGE_MAX + 2];
    fileEntry *files;
    boolean retval = false, again;
    cellDisplayBuffer dbuf[COLS][ROWS], rbuf[COLS][ROWS];
    color *dialogColor = &interfaceBoxColor;
    char *membuf;
    char fileDate [11];

    suffixLength = strlen(suffix);
    files = listFiles(&count, &membuf);
    copyDisplayBuffer(rbuf, displayBuffer);
    maxPathLength = strLenWithoutEscapes(prompt);

    // First, we want to filter the list by stripping out any filenames that do not end with suffix.
    // i is the entry we're testing, and j is the entry that we move it to if it qualifies.
    for (i=0, j=0; i<count; i++) {
        pathLength = strlen(files[i].path);
        //printf("\nString 1: %s", &(files[i].path[(max(0, pathLength - suffixLength))]));
        if (stringsExactlyMatch(&(files[i].path[(max(0, pathLength - suffixLength))]), suffix)) {

            // This file counts!
            if (i > j) {
                files[j] = files[i];
                //strftime(fileDate, sizeof(fileDate), DATE_FORMAT, &files[j].date);
                //printf("\nMatching file: %s\twith date: %s", files[j].path, fileDate);
            }
            j++;

            // Keep track of the longest length.
            if (min(pathLength, MAX_FILENAME_DISPLAY_LENGTH) + 10 > maxPathLength) {
                maxPathLength = min(pathLength, MAX_FILENAME_DISPLAY_LENGTH) + 10;
            }
        }
    }
    count = j;

    // Once we have all relevant files, we sort them by date descending
    qsort(files, count, sizeof(fileEntry), &fileEntryCompareDates);

    currentPageStart = 0;

    do { // Repeat to permit scrolling.
        again = false;

        for (i=0; i<min(count - currentPageStart, FILES_ON_PAGE_MAX); i++) {
            initializeButton(&(buttons[i]));
            buttons[i].flags &= ~(B_WIDE_CLICK_AREA | B_GRADIENT);
            buttons[i].buttonColor = *dialogColor;
            if (KEYBOARD_LABELS) {
                sprintf(buttons[i].text, "%c) ", 'a' + i);
            } else {
                buttons[i].text[0] = '\0';
            }
            strncat(buttons[i].text, files[currentPageStart+i].path, MAX_FILENAME_DISPLAY_LENGTH);

            // Clip off the file suffix from the button text.
            buttons[i].text[strlen(buttons[i].text) - suffixLength] = '\0'; // Snip!
            buttons[i].hotkey[0] = 'a' + i;
            buttons[i].hotkey[1] = 'A' + i;

            // Clip the filename length if necessary.
            if (strlen(buttons[i].text) > MAX_FILENAME_DISPLAY_LENGTH) {
                strcpy(&(buttons[i].text[MAX_FILENAME_DISPLAY_LENGTH - 3]), "...");
            }

            //strftime(fileDate, sizeof(fileDate), DATE_FORMAT, &files[currentPageStart+i].date);
            //printf("\nFound file: %s, with date: %s", files[currentPageStart+i].path, fileDate);
        }

        x = (COLS - maxPathLength) / 2;
        width = maxPathLength;
        height = min(count - currentPageStart, FILES_ON_PAGE_MAX) + 2;
        y = max(4, (ROWS - height) / 2);

        for (i=0; i<min(count - currentPageStart, FILES_ON_PAGE_MAX); i++) {
            pathLength = strlen(buttons[i].text);
            for (j=pathLength; j<(width - 10); j++) {
                buttons[i].text[j] = ' ';
            }
            buttons[i].text[j] = '\0';
            strftime(fileDate, sizeof(fileDate), DATE_FORMAT, &files[currentPageStart+i].date);
            strcpy(&(buttons[i].text[j]), fileDate);
            buttons[i].x = x;
            buttons[i].y = y + 1 + i;
        }

        if (count > FILES_ON_PAGE_MAX) {
            // Create up and down arrows.
            initializeButton(&(buttons[i]));
            strcpy(buttons[i].text, "     *     ");
            buttons[i].symbol[0] = G_UP_ARROW;
            if (currentPageStart <= 0) {
                buttons[i].flags &= ~(B_ENABLED | B_DRAW);
            } else {
                buttons[i].hotkey[0] = UP_ARROW;
                buttons[i].hotkey[1] = NUMPAD_8;
                buttons[i].hotkey[2] = PAGE_UP_KEY;
            }
            buttons[i].x = x + (width - 11)/2;
            buttons[i].y = y;

            i++;
            initializeButton(&(buttons[i]));
            strcpy(buttons[i].text, "     *     ");
            buttons[i].symbol[0] = G_DOWN_ARROW;
            if (currentPageStart + FILES_ON_PAGE_MAX >= count) {
                buttons[i].flags &= ~(B_ENABLED | B_DRAW);
            } else {
                buttons[i].hotkey[0] = DOWN_ARROW;
                buttons[i].hotkey[1] = NUMPAD_2;
                buttons[i].hotkey[2] = PAGE_DOWN_KEY;
            }
            buttons[i].x = x + (width - 11)/2;
            buttons[i].y = y + i;
        }

        if (count) {
            clearDisplayBuffer(dbuf);
            printString(prompt, x, y - 1, &itemMessageColor, dialogColor, dbuf);
            rectangularShading(x - 1, y - 1, width + 1, height + 1, dialogColor, INTERFACE_OPACITY, dbuf);
            overlayDisplayBuffer(dbuf, NULL);

//          for (j=0; j<min(count - currentPageStart, FILES_ON_PAGE_MAX); j++) {
//              strftime(fileDate, sizeof(fileDate), DATE_FORMAT, &files[currentPageStart+j].date);
//              printf("\nSanity check BEFORE: %s, with date: %s", files[currentPageStart+j].path, fileDate);
//              printf("\n   (button name)Sanity check BEFORE: %s", buttons[j].text);
//          }

            i = buttonInputLoop(buttons,
                                min(count - currentPageStart, FILES_ON_PAGE_MAX) + (count > FILES_ON_PAGE_MAX ? 2 : 0),
                                x,
                                y,
                                width,
                                height,
                                NULL);

//          for (j=0; j<min(count - currentPageStart, FILES_ON_PAGE_MAX); j++) {
//              strftime(fileDate, sizeof(fileDate), DATE_FORMAT, &files[currentPageStart+j].date);
//              printf("\nSanity check AFTER: %s, with date: %s", files[currentPageStart+j].path, fileDate);
//              printf("\n   (button name)Sanity check AFTER: %s", buttons[j].text);
//          }

            overlayDisplayBuffer(rbuf, NULL);

            if (i < min(count - currentPageStart, FILES_ON_PAGE_MAX)) {
                if (i >= 0) {
                    retval = true;
                    strcpy(path, files[currentPageStart+i].path);
                } else { // i is -1
                    retval = false;
                }
            } else if (i == min(count - currentPageStart, FILES_ON_PAGE_MAX)) { // Up arrow
                again = true;
                currentPageStart -= FILES_ON_PAGE_MAX;
            } else if (i == min(count - currentPageStart, FILES_ON_PAGE_MAX) + 1) { // Down arrow
                again = true;
                currentPageStart += FILES_ON_PAGE_MAX;
            }
        }

    } while (again);

    free(files);
    free(membuf);

    if (count == 0) {
        dialogAlert("No applicable files found.");
        return false;
    } else {
        return retval;
    }
}

// This is the basic program loop.
// When the program launches, or when a game ends, you end up here.
// If the player has already said what he wants to do next
// (by storing it in rogue.nextGame -- possibilities listed in enum NGCommands),
// we'll do it. The path (rogue.nextGamePath) is essentially a parameter for this command, and
// tells NG_VIEW_RECORDING and NG_OPEN_GAME which file to open. If there is a command but no
// accompanying path, and it's a command that should take a path, then pop up a dialog to have
// the player specify a path. If there is no command (i.e. if rogue.nextGame contains NG_NOTHING),
// then we'll display the title screen so the player can choose.
void mainBrogueJunction() {
    rogueEvent theEvent;
    char path[BROGUE_FILENAME_MAX], buf[100], seedDefault[100];
    char *maxSeed = "4294967295"; // 2^32 - 1
    short i, j, k;
    boolean seedTooBig;

    // clear screen and display buffer
    for (i=0; i<COLS; i++) {
        for (j=0; j<ROWS; j++) {
            displayBuffer[i][j].character = 0;
            displayBuffer[i][j].needsUpdate = false;
            displayBuffer[i][j].opacity = 100;
            for (k=0; k<3; k++) {
                displayBuffer[i][j].foreColorComponents[k] = 0;
                displayBuffer[i][j].backColorComponents[k] = 0;
            }
            plotCharWithColor(' ', i, j, &black, &black);
        }
    }

    initializeLaunchArguments(&rogue.nextGame, rogue.nextGamePath, &rogue.nextGameSeed);

    do {
        rogue.gameHasEnded = false;
        rogue.playbackFastForward = false;
        rogue.playbackMode = false;
        switch (rogue.nextGame) {
            case NG_NOTHING:
                // Run the main menu to get a decision out of the player.
                titleMenu();
                break;
            case NG_NEW_GAME:
            case NG_NEW_GAME_WITH_SEED:
                rogue.nextGamePath[0] = '\0';
                randomNumbersGenerated = 0;

                rogue.playbackMode = false;
                rogue.playbackFastForward = false;
                rogue.playbackBetweenTurns = false;

                getAvailableFilePath(path, LAST_GAME_NAME, GAME_SUFFIX);
                strcat(path, GAME_SUFFIX);
                strcpy(currentFilePath, path);

                if (rogue.nextGame == NG_NEW_GAME_WITH_SEED) {
                    if (rogue.nextGameSeed == 0) { // Prompt for seed; default is the previous game's seed.
                        if (previousGameSeed == 0) {
                            seedDefault[0] = '\0';
                        } else {
                            sprintf(seedDefault, "%lu", previousGameSeed);
                        }
                        if (getInputTextString(buf, "Generate dungeon with seed number:",
                                               strlen(maxSeed),
                                               seedDefault,
                                               "",
                                               TEXT_INPUT_NUMBERS,
                                               true)
                            && buf[0] != '\0') {
                            seedTooBig = false;
                            if (strlen(buf) == strlen(maxSeed)) {
                                for (i=0; maxSeed[i]; i++) {
                                    if (maxSeed[i] > buf[i]) {
                                        break; // we're good
                                    } else if (maxSeed[i] < buf[i]) {
                                        seedTooBig = true;
                                        break;
                                    }
                                }
                            }
                            sscanf(seedTooBig ? maxSeed : buf, "%lu", &rogue.nextGameSeed);
                        } else {
                            rogue.nextGame = NG_NOTHING;
                            break; // Don't start a new game after all.
                        }
                    }
                } else {
                    rogue.nextGameSeed = 0; // Seed based on clock.
                }

                rogue.nextGame = NG_NOTHING;
                initializeRogue(rogue.nextGameSeed);
                startLevel(rogue.depthLevel, 1); // descending into level 1

                mainInputLoop();
                if(serverMode) {
                    rogue.nextGame = NG_QUIT;
                }
                freeEverything();
                break;
            case NG_OPEN_GAME:
                rogue.nextGame = NG_NOTHING;
                path[0] = '\0';
                if (rogue.nextGamePath[0]) {
                    strcpy(path, rogue.nextGamePath);
                    rogue.nextGamePath[0] = '\0';
                } else {
                    dialogChooseFile(path, GAME_SUFFIX, "Open saved game:");
                    //chooseFile(path, "Open saved game: ", "Saved game", GAME_SUFFIX);
                }

                if (openFile(path)) {
                    if (loadSavedGame()) {
                        mainInputLoop();
                    }
                    freeEverything();
                } else {
                    //dialogAlert("File not found.");
                }
                rogue.playbackMode = false;
                rogue.playbackOOS = false;

                if(serverMode) {
                    rogue.nextGame = NG_QUIT;
                }
                break;
            case NG_VIEW_RECORDING:
                rogue.nextGame = NG_NOTHING;

                path[0] = '\0';
                if (rogue.nextGamePath[0]) {
                    strcpy(path, rogue.nextGamePath);
                    rogue.nextGamePath[0] = '\0';
                } else {
                    dialogChooseFile(path, RECORDING_SUFFIX, "View recording:");
                    //chooseFile(path, "View recording: ", "Recording", RECORDING_SUFFIX);
                }

                if (openFile(path)) {
                    randomNumbersGenerated = 0;
                    rogue.playbackMode = true;
                    initializeRogue(0); // Seed argument is ignored because we're in playback.
                    if (!rogue.gameHasEnded) {
                        startLevel(rogue.depthLevel, 1);
                        rogue.playbackPaused = true;
                        displayAnnotation(); // in case there's an annotation for turn 0
                    }

                    while(!rogue.gameHasEnded && rogue.playbackMode) {
                        if (rogue.playbackPaused) {
                            rogue.playbackPaused = false;
                            pausePlayback();
                        }
#ifdef ENABLE_PLAYBACK_SWITCH
                        // We are coming from the end of a recording the user has taken over.
                        // No more event checks, that has already been handled
                        if (rogue.gameHasEnded) {
                            break;
                        }
#endif
                        rogue.RNG = RNG_COSMETIC; // dancing terrain colors can't influence recordings
                        rogue.playbackBetweenTurns = true;
                        nextBrogueEvent(&theEvent, false, true, false);
                        rogue.RNG = RNG_SUBSTANTIVE;

                        executeEvent(&theEvent);
                    }

                    freeEverything();
                } else {
                    // announce file not found
                }
                rogue.playbackMode = false;
                rogue.playbackOOS = false;

                if(serverMode) {
                    rogue.nextGame = NG_QUIT;
                }
                break;
            case NG_HIGH_SCORES:
                rogue.nextGame = NG_NOTHING;
                printHighScores(false);
                break;
            case NG_QUIT:
                // No need to do anything.
                break;
            default:
                break;
        }
    } while (rogue.nextGame != NG_QUIT);
}