paulo@0: /* PC option screen for LOCKJAW, an implementation of the Soviet Mind Game paulo@0: paulo@0: Copyright (C) 2006 Damian Yerrick paulo@0: paulo@0: This work is free software; you can redistribute it and/or modify paulo@0: it under the terms of the GNU General Public License as published by paulo@0: the Free Software Foundation; either version 2 of the License, or paulo@0: (at your option) any later version. paulo@0: paulo@0: This program is distributed in the hope that it will be useful, paulo@0: but WITHOUT ANY WARRANTY; without even the implied warranty of paulo@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the paulo@0: GNU General Public License for more details. paulo@0: paulo@0: You should have received a copy of the GNU General Public License paulo@0: along with this program; if not, write to the Free Software paulo@0: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA paulo@0: paulo@0: Original game concept and design by Alexey Pajitnov. paulo@0: The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, paulo@0: or The Tetris Company LLC. paulo@0: paulo@0: */ paulo@0: paulo@0: #include "ljpc.h" paulo@0: #include paulo@0: #include "ljpath.h" paulo@0: #include paulo@0: #include "options.h" paulo@0: #include "ljlocale.h" paulo@0: paulo@0: #define OPTIONS_OFFSET 0 paulo@0: #define optionsMenu (commonOptionsMenu + OPTIONS_OFFSET) paulo@0: paulo@0: paulo@0: paulo@0: #if 0 paulo@0: static int getOptionValueByName(int row, const char *value) { paulo@0: for (int x = 0; x < optionsMenu[row].nValues; ++x) { paulo@0: if (!ustrcmp(value, optionsMenu[row].valueNames[x])) { paulo@0: return x + optionsMenu[row].minValue; paulo@0: } paulo@0: } paulo@0: return -1; paulo@0: } paulo@0: paulo@0: static void setOptionValueByNumber(struct LJPrefs *prefs, paulo@0: int row, const char *value) { paulo@0: if (value[0] >= '0' && value[0] <= '9') { paulo@0: int num = atoi(value); paulo@0: if (num >= optionsMenu[row].minValue paulo@0: && num < optionsMenu[row].minValue + optionsMenu[row].nValues) { paulo@0: prefs->number[row] = num; paulo@0: } paulo@0: } paulo@0: } paulo@0: paulo@0: static void setOptionValueByName(struct LJPrefs *prefs, paulo@0: int row, const char *value) { paulo@0: int num = getOptionValueByName(row, value); paulo@0: paulo@0: if (num >= 0) { paulo@0: prefs->number[row] = num; paulo@0: } paulo@0: } paulo@0: paulo@0: static void setOptionValueByBoolean(struct LJPrefs *prefs, paulo@0: int row, const char *value) { paulo@0: int num = atoi(value); paulo@0: paulo@0: if (num >= 0) { paulo@0: prefs->number[row] = num ? 1 : 0; paulo@0: } paulo@0: } paulo@0: #endif paulo@0: paulo@0: /* parse_ini_line ******************* paulo@0: */ paulo@0: int parse_ini_line(const char *in, char *key, char *var, char *val) paulo@0: { paulo@0: int c; paulo@0: char *kstart = key; paulo@0: paulo@0: /* Skip whitespace before key */ paulo@0: while(*in && isspace(*in)) paulo@0: in++; paulo@0: paulo@0: /* Parse key */ paulo@0: if(*in == '[') /* if we have a new key, load it */ paulo@0: { paulo@0: in++; paulo@0: paulo@0: /* Skip whitespace before actual key */ paulo@0: while(*in && isspace(*in)) paulo@0: in++; paulo@0: paulo@0: for(c = *in++; paulo@0: c != 0 && c != ']' && c != '\n' && c != '\r'; paulo@0: c = *in++) paulo@0: { paulo@0: if(!isspace(c)) paulo@0: *key++ = c; paulo@0: } paulo@0: *key = 0; paulo@0: /* Strip whitespace after key */ paulo@0: do { paulo@0: *key-- = 0; paulo@0: } while(key >= kstart && isspace(*key)); paulo@0: } paulo@0: paulo@0: /* Skip whitespace before variable */ paulo@0: while(*in && isspace(*in)) paulo@0: in++; paulo@0: if(*in == 0) /* if there is no variable, don't do anything */ paulo@0: return 0; paulo@0: paulo@0: for(c = *in++; paulo@0: c != 0 && c != '=' && c != '\n' && c != '\r'; paulo@0: c = *in++) paulo@0: { paulo@0: if(!isspace(c)) paulo@0: { paulo@0: *var++ = c; paulo@0: } paulo@0: } paulo@0: *var = 0; paulo@0: paulo@0: /* Skip whitespace before value */ paulo@0: while(*in && isspace(*in)) paulo@0: in++; paulo@0: paulo@0: /* Get value */ paulo@0: kstart = val; paulo@0: for(c = *in++; paulo@0: c != 0 && c != '\n' && c != '\r'; paulo@0: c = *in++) paulo@0: { paulo@0: *val++ = c; paulo@0: } paulo@0: /* Strip whitespace after value */ paulo@0: do { paulo@0: *val-- = 0; paulo@0: } while(val >= kstart && isspace(*val)); paulo@0: paulo@0: return 0; paulo@0: } paulo@0: paulo@0: static const char ljIniName[] = "lj.ini"; paulo@0: paulo@0: /** paulo@0: * Finds a FourCC in a list. paulo@0: * @param needle the fourCC to search for paulo@0: * @param haystack a list of fourCCs paulo@0: * @param haystackLen the length of this list paulo@0: * @return the index within haystack where needle was found paulo@0: * or -1 if not found paulo@0: */ paulo@0: int findFourCC(const char *needle, paulo@0: const FourCC *haystack, size_t haystackLen) { paulo@0: for (size_t i = 0; i < haystackLen; ++i) { paulo@0: if (!strncmp(haystack[i].c, needle, 4)) { paulo@0: return i; paulo@0: } paulo@0: } paulo@0: return -1; paulo@0: } paulo@0: paulo@0: void loadOption(struct LJPrefs *prefs, paulo@0: const char *name, const char *value) { paulo@0: for (int i = 0; i < PC_OPTIONS_MENU_LEN; ++i) { paulo@0: if (!strncmp(optionsMenu[i].name.c, name, 4)) { paulo@0: if (isdigit(value[0])) { paulo@0: unsigned long int n = strtoul(value, NULL, 10); paulo@0: if (n >= optionsMenu[i].minValue paulo@0: && n < optionsMenu[i].minValue + optionsMenu[i].nValues) { paulo@0: prefs->number[i] = n; paulo@0: } else { paulo@0: allegro_message("ignoring out of range %s=%lu\n", paulo@0: name, n); paulo@0: } paulo@0: } else if (optionsMenu[i].valueNames) { paulo@0: int nValueNames = (optionsMenu[i].style == OPTSTYLE_FRAMES paulo@0: ? 2 paulo@0: : optionsMenu[i].nValues); paulo@0: int foundValue = findFourCC(value, optionsMenu[i].valueNames, nValueNames); paulo@0: if (optionsMenu[i].style == OPTSTYLE_FRAMES paulo@0: && foundValue > 0) { paulo@0: foundValue = optionsMenu[i].nValues - 1; paulo@0: } paulo@0: prefs->number[i] = foundValue + optionsMenu[i].minValue; paulo@0: } paulo@0: return; paulo@0: } paulo@0: } paulo@0: } paulo@0: paulo@0: int loadOptions(struct LJPrefs *prefs) { paulo@0: FILE *fp = ljfopen(ljIniName, "rt"); paulo@0: char key[1024], var[1024], val[1024], input_buf[1024]; paulo@0: paulo@0: if (!fp) return 0; paulo@0: paulo@0: key[0] = 0; paulo@0: var[0] = 0; paulo@0: val[0] = 0; paulo@0: paulo@0: while(fgets (input_buf, sizeof(input_buf), fp)) { paulo@0: parse_ini_line(input_buf, key, var, val); paulo@0: paulo@0: if(!ustrcmp ("Skin", var)) { paulo@0: ustrzcpy(skinName, sizeof(skinName) - 1, val); paulo@0: } else { paulo@0: loadOption(prefs, var, val); paulo@0: } paulo@0: } paulo@0: fclose(fp); paulo@0: paulo@0: if (prefs->number[OPTIONS_SIDEWAYS_DELAY] < prefs->number[OPTIONS_SIDEWAYS_SPEED]) { paulo@0: prefs->number[OPTIONS_SIDEWAYS_DELAY] = prefs->number[OPTIONS_SIDEWAYS_SPEED]; paulo@0: } paulo@0: paulo@0: return 0; paulo@0: } paulo@0: paulo@0: void saveOption(int i, int n, FILE *out) { paulo@0: char name[8], value[8]; paulo@0: int nameIdx = -1; paulo@0: paulo@0: strncpy(name, optionsMenu[i].name.c, 4); paulo@0: name[4] = 0; paulo@0: paulo@0: if (optionsMenu[i].valueNames) { paulo@0: if (optionsMenu[i].style == OPTSTYLE_FRAMES) { paulo@0: if (n == optionsMenu[i].minValue) { paulo@0: nameIdx = 0; paulo@0: } else if (n == optionsMenu[i].minValue + optionsMenu[i].nValues - 1) { paulo@0: nameIdx = 1; paulo@0: } paulo@0: } else { paulo@0: nameIdx = n - optionsMenu[i].minValue; paulo@0: } paulo@0: if (nameIdx >= 0 paulo@0: && optionsMenu[i].valueNames[nameIdx].i == 0) { paulo@0: nameIdx = -1; paulo@0: } paulo@0: } paulo@0: paulo@0: if (nameIdx >= 0) { paulo@0: strncpy(value, optionsMenu[i].valueNames[nameIdx].c, 4); paulo@0: value[4] = 0; paulo@0: fprintf(out, "%s=%s\n", paulo@0: name, value); paulo@0: } else { paulo@0: fprintf(out, "%s=%d\n", paulo@0: name, n); paulo@0: } paulo@0: } paulo@0: paulo@0: void saveOptions(const struct LJPrefs *prefs) { paulo@0: FILE *out = ljfopen(ljIniName, "wt"); paulo@0: paulo@0: if (out) { paulo@0: for (int i = 0; i < PC_OPTIONS_MENU_LEN; ++i) { paulo@0: saveOption(i, prefs->number[i], out); paulo@0: } paulo@0: fprintf(out, "Skin=%s\n", paulo@0: skinName); paulo@0: fclose(out); paulo@0: } paulo@0: } paulo@0: paulo@0: void unpackOptions(LJView *v, const struct LJPrefs *prefs) { paulo@0: unpackCommonOptions(v, prefs->number); paulo@0: paulo@0: v->plat->nextAbove = prefs->number[OPTIONS_NEXT_ABOVE]; paulo@0: v->showTrails = prefs->number[OPTIONS_TRAILS]; paulo@0: autoPause = prefs->number[OPTIONS_AUTO_PAUSE]; paulo@0: } paulo@0: paulo@0: /******************************************************************** paulo@0: paulo@0: Allegro based option drawing code paulo@0: paulo@0: ********************************************************************/ paulo@0: paulo@0: #define OPTIONS_TOP 100 paulo@0: #define OPTIONS_ROW_HT 40 paulo@0: #define OPTIONS_ROW_LEFT 80 paulo@0: #define OPTIONS_ROW_MID 400 paulo@0: #define OPTIONS_ROW_RIGHT 760 paulo@0: #define OPTIONS_MENU_VIS 7 paulo@0: #define OPTIONS_FONT aver32 paulo@0: paulo@0: paulo@0: /** paulo@0: * @param y number of option to draw paulo@0: * @param hilite bitfield: 1=focused, 0=not paulo@0: */ paulo@0: void optionsDrawRow(const unsigned char *prefs, paulo@0: int dstY, int line, int value, int hilite) { paulo@0: unsigned int ht = text_height(OPTIONS_FONT); paulo@0: int buttonY = OPTIONS_TOP + OPTIONS_ROW_HT * dstY; paulo@0: int rowBg = bgColor; paulo@0: const char *nameText; paulo@0: char altNameText[8]; paulo@0: char valueText[OPTIONS_VALUE_LEN]; paulo@0: const char *valueOverride = isDisabledOption(prefs, line); paulo@0: int textcolor = fgColor; paulo@0: const char *valueDesc = NULL; paulo@0: paulo@0: { paulo@0: nameText = ljGetFourCCName(optionsMenu[line].name); paulo@0: if (!nameText) { paulo@0: strncpy(altNameText, optionsMenu[line].name.c, 4); paulo@0: altNameText[4] = 0; paulo@0: nameText = altNameText; paulo@0: } paulo@0: } paulo@0: paulo@0: if (valueOverride) { paulo@0: hilite |= 2; paulo@0: textcolor = makecol(128, 128, 128); paulo@0: } paulo@0: paulo@0: if (hilite == 3) { paulo@0: rowBg = makecol(204, 204, 204); paulo@0: } else if (hilite == 1) { paulo@0: rowBg = hiliteColor; paulo@0: } paulo@0: paulo@0: // If the value of this option is within range, format it as a string. paulo@0: if (value >= optionsMenu[line].minValue paulo@0: && value < optionsMenu[line].minValue + optionsMenu[line].nValues) { paulo@0: if (valueOverride) { paulo@0: ustrzcpy(valueText, sizeof(valueText), "overridden"); paulo@0: valueDesc = valueOverride; paulo@0: } else { paulo@0: valueDesc = getOptionsValueStr(valueText, line + OPTIONS_OFFSET, value); paulo@0: } paulo@0: } else { paulo@0: valueText[0] = '\0'; paulo@0: } paulo@0: paulo@0: // draw current option paulo@0: acquire_screen(); paulo@0: rectfill(screen, paulo@0: OPTIONS_ROW_LEFT, buttonY, paulo@0: OPTIONS_ROW_RIGHT - 1, buttonY + OPTIONS_ROW_HT - 1, paulo@0: rowBg); paulo@0: textout_ex(screen, OPTIONS_FONT, nameText, paulo@0: OPTIONS_ROW_LEFT + 8, buttonY + (OPTIONS_ROW_HT - ht) / 2, paulo@0: textcolor, rowBg); paulo@0: textout_ex(screen, OPTIONS_FONT, paulo@0: valueText, paulo@0: OPTIONS_ROW_MID, buttonY + (OPTIONS_ROW_HT - ht) / 2, paulo@0: textcolor, rowBg); paulo@0: paulo@0: // For an enabled selected item, draw the frame paulo@0: if (hilite == 1) { paulo@0: rect(screen, paulo@0: OPTIONS_ROW_LEFT, buttonY, paulo@0: OPTIONS_ROW_RIGHT - 1, buttonY + OPTIONS_ROW_HT - 1, paulo@0: fgColor); paulo@0: } paulo@0: paulo@0: // For a selected item, draw the help text paulo@0: if (hilite & 1) { paulo@0: buttonY = OPTIONS_TOP + OPTIONS_ROW_HT * OPTIONS_MENU_VIS; paulo@0: rectfill(screen, paulo@0: OPTIONS_ROW_LEFT, buttonY, paulo@0: OPTIONS_ROW_RIGHT - 1, buttonY + OPTIONS_ROW_HT * 5 / 2 - 1, paulo@0: bgColor); paulo@0: paulo@0: const char *descText = ljGetFourCCDesc(optionsMenu[line].name); paulo@0: if (descText) { paulo@0: textout_ex(screen, OPTIONS_FONT, descText, paulo@0: OPTIONS_ROW_LEFT, paulo@0: buttonY + OPTIONS_ROW_HT - ht / 2, paulo@0: textcolor, bgColor); paulo@0: } paulo@0: if (valueDesc) { paulo@0: textout_ex(screen, OPTIONS_FONT, valueDesc, paulo@0: OPTIONS_ROW_LEFT, paulo@0: buttonY + 2 * OPTIONS_ROW_HT - ht / 2, paulo@0: textcolor, bgColor); paulo@0: } paulo@0: } paulo@0: paulo@0: release_screen(); paulo@0: } paulo@0: paulo@0: void optionsClearRow(int dstY, int hilite) { paulo@0: int buttonY = OPTIONS_TOP + OPTIONS_ROW_HT * dstY; paulo@0: rectfill(screen, paulo@0: OPTIONS_ROW_LEFT, buttonY, paulo@0: OPTIONS_ROW_RIGHT - 1, buttonY + OPTIONS_ROW_HT * 5 / 2 - 1, paulo@0: bgColor); paulo@0: } paulo@0: paulo@0: void optionsDrawPage(int page, const unsigned char *prefs) { paulo@0: int nPages = 0; paulo@0: for (; optionsPages[nPages].name; ++nPages) { } paulo@0: paulo@0: vsync(); paulo@0: acquire_screen(); paulo@0: rectfill(screen, 320, 32, SCREEN_W - 1, 63, bgColor); paulo@0: textprintf_right_ex(screen, aver32, paulo@0: SCREEN_W - 16, 32, fgColor, -1, paulo@0: "(%d/%d) %s", paulo@0: page + 1, nPages, optionsPages[page].name); paulo@0: paulo@0: for (int i = optionsPages[page].start; paulo@0: i < optionsPages[page + 1].start; ++i) { paulo@0: optionsDrawRow(prefs, i - optionsPages[page].start, paulo@0: i, prefs[i], 0); paulo@0: } paulo@0: for (int i = optionsPages[page + 1].start; paulo@0: i < optionsPages[page].start + OPTIONS_MENU_VIS; ++i) { paulo@0: optionsClearRow(i - optionsPages[page].start, 0); paulo@0: } paulo@0: release_screen(); paulo@0: } paulo@0: paulo@0: void optionsWinInit(void) { paulo@0: acquire_screen(); paulo@0: clear_to_color(screen, bgColor); paulo@0: textout_ex(screen, aver32, "LOCKJAW > Options", 16, 32, fgColor, -1); paulo@0: textout_ex(screen, aver32, "Rotate: change page; Up/Down: select", 16, 522, fgColor, -1); paulo@0: textout_ex(screen, aver32, "Left/Right change; Enter: exit", 16, 552, fgColor, -1); paulo@0: release_screen(); paulo@0: paulo@0: } paulo@0: paulo@0: void optionsIdle(void) { paulo@0: rest(10); paulo@0: }