SuperSpaceInvaderTurboApoca.../src/configManagement.cpp
2022-01-02 13:26:00 +01:00

247 lines
7.6 KiB
C++

#include <fstream>
#include "game.h"
class ConfigBuilder{
public:
ConfigData collectedData;
void parseFile(const string& fname);
void readConfig();
void dumpInternalValues();
private:
map<string, string> internalValues;
string& getString(const configKey& key);
char getChar(const configKey& key);
int getInt(const configKey& key);
nsGraphics::RGBAcolor getColor(const configKey& key);
void getList(const configKey& key, vector<string>&);
};
void ConfigBuilder::dumpInternalValues(){
for(const auto& ite : internalValues){
cerr << ite.first << " -> " << ite.second << endl;
}
}
void trimSpaces(string& str){
str.erase(0, str.find_first_not_of(' '));
}
/*
* WARNING : This implementation of YAML is not meant to detect and report errors in a non YAML-compliant file
*/
void ConfigBuilder::parseFile(const string& fname) {
ifstream file(fname);
if(!file.is_open())throw runtime_error("Error while opening config.yml. Check file location ?");
vector<string> keyParts;
unsigned listIndex;
while (!file.eof()) {
string line;
getline(file, line);
auto match = line.find('#');
if (match != string::npos)line.erase(match);
if (line.empty())continue;
unsigned currentIndent = 0;
while (line[currentIndent] == ' ')++currentIndent;
if(line[currentIndent]=='-'){
string value = line.substr(currentIndent+1);
trimSpaces(value);
string fullKey;
for (unsigned i = 0; i < currentIndent; ++i) {
fullKey.append(keyParts[i]);
fullKey.append(".");
}
// lists are just treated as sections with key 0,1,2...
fullKey.append(to_string((listIndex)));
++listIndex;
internalValues[fullKey] = value;
}else{
match = line.find(':');
if (match == string::npos)throw runtime_error("Invalid line : " + line);
string key = line.substr(0, match);
string value = line.substr(match + 1);
trimSpaces(key);
trimSpaces(value);
if (value.empty()) {
keyParts.resize(currentIndent);
keyParts.push_back(key);
listIndex = 0;
} else {
string fullKey;
for (unsigned i = 0; i < currentIndent; ++i) {
fullKey.append(keyParts[i]);
fullKey.append(".");
}
fullKey.append(key);
internalValues[fullKey] = value;
}
}
}
file.close();
}
void ConfigBuilder::readConfig() {
vector<string> tmp;
getList("grid", tmp);
// we are essentially going to translate a line-oriented config to a column-oriented grid
unsigned maxSize = 0;
for(string& s : tmp){
if(s.size()>maxSize)maxSize = s.size();
}
collectedData.grid.resize(maxSize);
for(string& s : tmp){
unsigned i=0;
for(;i<s.size();++i){
switch(toupper(s[i])){
case 'A':{
collectedData.grid[i].push_back(Invader::TYPEA);
break;
}
case 'B':{
collectedData.grid[i].push_back(Invader::TYPEB);
break;
}
case 'C':{
collectedData.grid[i].push_back(Invader::TYPEC);
break;
}
case ' ':{
collectedData.grid[i].push_back(Invader::NONE);
break;
}
default:{
throw runtime_error("Invalid invader ID in grid definition : "+ to_string(s[i]));
}
}
}
while(i<maxSize){
collectedData.grid[i].push_back(Invader::NONE);
++i;
}
}
// players
collectedData.playersWidth = getInt("players.width");
collectedData.startXPosition = getInt("players.startXPosition");
collectedData.playersSpeed = getInt("players.speed");
collectedData.playersFireCooldown = getInt("players.fireCooldown");
// the scalability behind the vector of players is only an illusion, because we force player count to be 1 or 2
// It was done so the 2+ players implementation could be easier in the future, if wanted
collectedData.playerDefs.resize(2);
collectedData.playerDefs[0].color = getColor("players.user1.color");
collectedData.playerDefs[0].keys.left = getChar("players.user1.keys.left");
collectedData.playerDefs[0].keys.right = getChar("players.user1.keys.right");
collectedData.playerDefs[0].keys.shoot = getChar("players.user1.keys.shoot");
collectedData.playerDefs[1].color = getColor("players.user2.color");
collectedData.playerDefs[1].keys.left = getChar("players.user2.keys.left");
collectedData.playerDefs[1].keys.right = getChar("players.user2.keys.right");
collectedData.playerDefs[1].keys.shoot = getChar("players.user2.keys.shoot");
// invaders
collectedData.invadersSize = getInt("invaders.size");
collectedData.invadersSpeed = getInt("invaders.speed");
collectedData.invadersDistance = getInt("invaders.distance");
collectedData.invadersFireCooldown = getInt("invaders.fireCooldown");
// projectiles
collectedData.missilesWidth = getInt("projectiles.missiles.width");
collectedData.missilesLength = collectedData.missilesWidth*PROJ_LENGTH_FACTOR;
collectedData.missilesSpeed = getInt("projectiles.missiles.speed");
collectedData.missilesColor = getColor("projectiles.missiles.color");
collectedData.torpedosWidth = getInt("projectiles.torpedos.width");
collectedData.torpedosLength = collectedData.torpedosWidth*PROJ_LENGTH_FACTOR;
collectedData.torpedosSpeed = getInt("projectiles.missiles.speed");
collectedData.torpedosColor = getColor("projectiles.torpedos.color");
}
int ConfigBuilder::getInt(const configKey& key) {
return stoi(getString(key));
}
char ConfigBuilder::getChar(const configKey& key) {
return getString(key)[0];
}
string& ConfigBuilder::getString(const configKey& key) {
if(internalValues.contains(key)){
return internalValues.at(key);
}else{
throw runtime_error("Non-existent key requested : "+key);
}
}
void ConfigBuilder::getList(const configKey& key, vector<string>& toPopulate) {
size_t i=0;
string fullKey = key+".0";
if(!internalValues.contains(fullKey))throw runtime_error("Non-existent list key requested : "+key);
do{
toPopulate.push_back(internalValues.at(fullKey));
++i;
fullKey = key+"."+to_string(i);
}while(internalValues.contains(key+"."+to_string(i)));
}
nsGraphics::RGBAcolor ConfigBuilder::getColor(const configKey& key) {
// switch do not work with strings, and I don't want to implement a constexpr hash function
string colorStr = getString(key);
if (colorStr == "black")return nsGraphics::KBlack;
else if (colorStr == "white")return nsGraphics::KWhite;
else if (colorStr == "red")return nsGraphics::KRed;
else if (colorStr == "lime")return nsGraphics::KLime;
else if (colorStr == "blue")return nsGraphics::KBlue;
else if (colorStr == "yellow")return nsGraphics::KYellow;
else if (colorStr == "cyan")return nsGraphics::KCyan;
else if (colorStr == "magenta")return nsGraphics::KMagenta;
else if (colorStr == "silver")return nsGraphics::KSilver;
else if (colorStr == "gray")return nsGraphics::KGray;
else if (colorStr == "maroon")return nsGraphics::KMaroon;
else if (colorStr == "olive")return nsGraphics::KOlive;
else if (colorStr == "green")return nsGraphics::KGreen;
else if (colorStr == "purple")return nsGraphics::KPurple;
else if (colorStr == "teal")return nsGraphics::KTeal;
else if (colorStr == "navy")return nsGraphics::KNavy;
else throw runtime_error("Invalid color string : "+colorStr);
}
/**
*
* @return false if there was an error
*/
bool Game::reloadConfig() {
map<string, string> strValues;
ConfigBuilder builder;
bool parsed = false;
try{
builder.parseFile("config.yml");
parsed = true;
builder.readConfig();
}catch(runtime_error& e){
if(parsed)cerr << "An error occured while reading the configuration :" << endl;
else cerr << "An error occured while parsing the configuration :" << endl;
cerr << e.what() << endl;
if(parsed){
cerr << "Parsed keys :" << endl;
builder.dumpInternalValues();
}
cerr << "(The old configuration was kept in memory)" << endl;
return false;
}
confData = builder.collectedData;
return true;
}