-
Notifications
You must be signed in to change notification settings - Fork 112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Saving and loading game settings from a config #639
base: release
Are you sure you want to change the base?
Changes from all commits
1059310
775644d
23106f4
670ea64
b5c9093
b405c98
3880110
fa158d4
d585aa1
d07763b
a5e84ef
481401c
68ed92c
65ff74a
2df2f4b
16b6bc7
09933fb
379e806
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Game settings are saved to a configuration file, persisting between game launches. The saved settings include game variant (vanilla or rapid), graphical mode, replay speed, color effects, stealth range visibility, wizard mode, and easy mode. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,255 @@ | ||
#include "../cjson/cJSON.h" | ||
#include "Rogue.h" | ||
#include "GlobalsBase.h" | ||
|
||
typedef struct configParams { | ||
short playbackDelayPerTurn; | ||
short gameVariant; | ||
short graphicsMode; | ||
boolean displayStealthRangeMode; | ||
boolean trueColorMode; | ||
boolean wizard; | ||
boolean easyMode; | ||
} configParams; | ||
|
||
typedef enum { | ||
INT_TYPE, | ||
BOOLEAN_TYPE, | ||
ENUM_STRING // a json string that needs to be mapped to an Enum | ||
} fieldType; | ||
|
||
typedef struct { | ||
int min; | ||
int max; | ||
} intRange; | ||
|
||
typedef union { | ||
const char** enumMappings; | ||
intRange intRange; | ||
} fieldValidator; | ||
|
||
typedef struct { | ||
const char* name; | ||
void* paramPointer; // a pointer to a field of configParams struct | ||
fieldType type; | ||
fieldValidator validator; | ||
} configField; | ||
|
||
static const char* JSON_FILENAME = "brogue_config.json"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Optional. How about brogue.config ? |
||
|
||
// maps json strings to the gameVariant Enum | ||
static const char* variantMappings[] = {"brogue", "rapid", NULL}; | ||
|
||
// maps json strings to the graphicsModes Enum | ||
static const char* graphicsModeMappings[] = {"text", "tiles", "hybrid", NULL}; | ||
|
||
static intRange playbackDelayRange = {MIN_PLAYBACK_DELAY, MAX_PLAYBACK_DELAY}; | ||
|
||
static configParams createDefaultConfig() { | ||
configParams config; | ||
|
||
config.playbackDelayPerTurn = DEFAULT_PLAYBACK_DELAY; | ||
config.gameVariant = VARIANT_BROGUE; | ||
config.graphicsMode = TEXT_GRAPHICS; | ||
config.displayStealthRangeMode = false; | ||
config.trueColorMode = false; | ||
config.easyMode = false; | ||
config.wizard = false; | ||
|
||
return config; | ||
} | ||
|
||
static char* loadConfigFile() { | ||
FILE* jsonFile = fopen(JSON_FILENAME, "r"); | ||
|
||
if (!jsonFile) { | ||
return NULL; | ||
} | ||
|
||
fseek(jsonFile, 0, SEEK_END); | ||
long fileSize = ftell(jsonFile); | ||
fseek(jsonFile, 0, SEEK_SET); | ||
|
||
char* buffer = (char*)malloc(fileSize + 1); | ||
|
||
if (!buffer) { | ||
fclose(jsonFile); | ||
return NULL; | ||
} | ||
|
||
fread(buffer, 1, fileSize, jsonFile); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Should probably check the number of bytes written by |
||
fclose(jsonFile); | ||
|
||
buffer[fileSize] = '\0'; | ||
|
||
return buffer; | ||
} | ||
|
||
static configField* getFieldEntries(configParams* config) { | ||
int numFields = 7; | ||
|
||
configField fieldDescriptors[] = { | ||
{"gameVariant", &(config->gameVariant), ENUM_STRING, {.enumMappings = variantMappings}}, | ||
{"graphicsMode", &(config->graphicsMode), ENUM_STRING, {.enumMappings = graphicsModeMappings}}, | ||
{"playbackDelayPerTurn", &(config->playbackDelayPerTurn), INT_TYPE, {.intRange = playbackDelayRange}}, | ||
{"displayStealthRangeMode", &(config->displayStealthRangeMode), BOOLEAN_TYPE}, | ||
{"trueColorMode", &(config->trueColorMode), BOOLEAN_TYPE}, | ||
{"wizard", &(config->wizard), BOOLEAN_TYPE}, | ||
{"easyMode", &(config->easyMode), BOOLEAN_TYPE}, | ||
{NULL}}; | ||
|
||
configField* entries = calloc(numFields + 1, sizeof(configField)); | ||
|
||
for (int i = 0; i < numFields; i++) { | ||
entries[i] = fieldDescriptors[i]; | ||
} | ||
|
||
return entries; | ||
} | ||
|
||
static short mapStringToEnum(const char* inputString, const char** mappings) { | ||
for (short i = 0; mappings[i]; i++) { | ||
if (strcmp(inputString, mappings[i]) == 0) { | ||
return i; | ||
} | ||
} | ||
return -1; | ||
} | ||
|
||
static void parseConfigValues(const char* jsonString, configParams* config) { | ||
if (!jsonString || !config) { | ||
return; // Invalid input | ||
} | ||
|
||
cJSON* root = cJSON_Parse(jsonString); | ||
|
||
if (!root) { | ||
return; // JSON parsing error | ||
} | ||
|
||
configField* entries = getFieldEntries(config); | ||
|
||
for (int i = 0; entries[i].name; i++) { | ||
cJSON* jsonField = cJSON_GetObjectItem(root, entries[i].name); | ||
|
||
if (jsonField) { | ||
switch (entries[i].type) { | ||
case INT_TYPE: | ||
if (cJSON_IsNumber(jsonField)) { | ||
short value = jsonField->valueint; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to check for overflow/underflow, since |
||
intRange valueRange = entries[i].validator.intRange; | ||
|
||
if (value >= valueRange.min && value <= valueRange.max) { | ||
*((short*)entries[i].paramPointer) = value; | ||
} | ||
} | ||
break; | ||
|
||
case BOOLEAN_TYPE: | ||
if (cJSON_IsBool(jsonField)) { | ||
*((boolean*)entries[i].paramPointer) = jsonField->valueint; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I'm reading the documentation for cJson correctly, I believe you need to check |
||
} | ||
break; | ||
|
||
case ENUM_STRING: | ||
if (cJSON_IsString(jsonField)) { | ||
const char* modeString = jsonField->valuestring; | ||
short mode = mapStringToEnum(modeString, entries[i].validator.enumMappings); | ||
|
||
if (mode != -1) { | ||
*((short*)entries[i].paramPointer) = mode; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if all of the enum types are definitely short-sized. It would be nice to have some better type-safety here, maybe a constructor function for enum struct field definitions that explicitly only accepts |
||
} | ||
} | ||
break; | ||
|
||
default: | ||
break; | ||
} | ||
} | ||
} | ||
|
||
free(entries); | ||
cJSON_Delete(root); | ||
} | ||
|
||
static char* createJsonString(configParams* config) { | ||
cJSON* root = cJSON_CreateObject(); | ||
|
||
configField* entries = getFieldEntries(config); | ||
|
||
for (int i = 0; entries[i].name; i++) { | ||
switch (entries[i].type) { | ||
case INT_TYPE: { | ||
short short_value = *((short*)entries[i].paramPointer); | ||
cJSON_AddNumberToObject(root, entries[i].name, short_value); | ||
break; | ||
} | ||
case BOOLEAN_TYPE: { | ||
boolean bool_value = *((boolean*)entries[i].paramPointer); | ||
cJSON_AddBoolToObject(root, entries[i].name, bool_value); | ||
break; | ||
} | ||
case ENUM_STRING: { | ||
short enum_value = *((short*)entries[i].paramPointer); | ||
const char* string_value = entries[i].validator.enumMappings[enum_value]; | ||
cJSON_AddStringToObject(root, entries[i].name, string_value); | ||
break; | ||
} | ||
|
||
default: | ||
break; | ||
} | ||
} | ||
|
||
char* jsonString = cJSON_Print(root); | ||
|
||
free(entries); | ||
cJSON_Delete(root); | ||
|
||
return jsonString; | ||
} | ||
|
||
void readFromConfig(enum graphicsModes* initialGraphics) { | ||
char* jsonString = loadConfigFile(); | ||
|
||
configParams config = createDefaultConfig(); | ||
|
||
parseConfigValues(jsonString, &config); | ||
|
||
rogue.wizard = config.wizard; | ||
rogue.easyMode = config.easyMode; | ||
rogue.displayStealthRangeMode = config.displayStealthRangeMode; | ||
rogue.trueColorMode = config.trueColorMode; | ||
rogue.playbackDelayPerTurn = config.playbackDelayPerTurn; | ||
|
||
gameVariant = config.gameVariant; | ||
*initialGraphics = config.graphicsMode; | ||
|
||
free(jsonString); | ||
} | ||
|
||
void writeIntoConfig() { | ||
configParams config; | ||
|
||
FILE* file = fopen(JSON_FILENAME, "w"); | ||
|
||
if (!file) { | ||
return; | ||
} | ||
|
||
config.wizard = rogue.wizard; | ||
config.easyMode = rogue.easyMode; | ||
config.displayStealthRangeMode = rogue.displayStealthRangeMode; | ||
config.trueColorMode = rogue.trueColorMode; | ||
config.playbackDelayPerTurn = rogue.playbackDelayPerTurn; | ||
|
||
config.gameVariant = gameVariant; | ||
config.graphicsMode = graphicsMode; | ||
|
||
char* jsonString = createJsonString(&config); | ||
|
||
fprintf(file, "%s", jsonString); | ||
|
||
fclose(file); | ||
free(jsonString); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional suggested naming:
stealthRangeEnabled
colorEffectsEnabled
wizardModeEnabled
easyModeEnabled