The Problem: I am trying to extract a valid game mode for Defense of the Ancients (DotA) from a game name using C++.
Details:
- Game names can be, at most, 31 characters long
- There are three game mode categories: primary, secondary, and miscellaneous
- There can only be 1 primary game mode selected
- Certain primary game modes are incompatible with some secondary game modes
- Certain secondary game modes are incompatible with other secondary game modes
- Miscellaneous game modes can be combined with all other game modes
Here are a list of the various game modes, with a chart showing which secondary modes each mode is compatible with (X means incompatible):
// Only 1 primary allowed
static char *Primary[] = {
// Compatible with > | dm | rv | mm | du | sh | aa | ai | as | id | em | np | sc | om | nt | nm | nb | ro | mo | sp |
"ap", // All Pick | | | | | | | | | | | | | | | | | | | |
"ar", // All Random | | X | | | | | | | | | | | | | | | | | |
"tr", // Team Random | X | X | | | | | | | | | | | | | | | | | |
"mr", // Mode Random | X | X | | | X | X | X | X | | | | | | | | | X | X | |
"lm", // League Mode | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | |
"rd", // Random Draft | X | X | X | | X | X | X | X | | | | | | | | | X | X | |
"vr", // Vote Random | X | X | X | | X | X | X | X | | | | | | | | | X | X | |
"el", // Extended League | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | |
"sd", // Single Draft | X | X | X | | X | X | X | X | | | | | | | | | X | X | |
"cm", // Captains Mode | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X |
"cd" // Captains Draft | X | X | X | | X | X | X | X | | | | | | | | | X | X | |
};
static char *Secondary[] = {
// Compatible with > | dm | rv | mm | du | sh | aa | ai | as | id | em | np | sc | om | nt | nm | nb | ro | mo | sp |
"dm", // Death Match | | X | X | | X | X | X | X | | | | | | | | | X | X | |
"rv", // Reverse Mode | X | | | | X | | | | | | | | | | | | | | |
"mm", // Mirror Match | X | | | | X | | | | | | | | | | | | | | |
"du", // Duplicate Mode | | | | | | | | | | | | | | | | | | | |
"sh", // Same Hero | X | X | X | | | | | | | | | | | | | | | | |
"aa", // All Agility | X | | | | | | X | X | | | | | | | | | | | |
"ai", // All Intelligence | X | | | | | X | | X | | | | | | | | | | | |
"as", // All Strength | X | | | | | X | X | | | | | | | | | | | | |
"id", // Item Drop | | | | | | | | | | | | | | | | | | | |
"em", // Easy Mode | | | | | | | | | | | | | | | | | | | |
"np", // No Powerups | | | | | | | | | | | | | | | | | | | |
"sc", // Super Creeps | | | | | | | | | | | | | | | | | | | |
"om", // Only Mid | | | | | | | | | | | | | | | | | | | |
"nt", // No Top | | | | | | | | | | | | | | | | | | | |
"nm", // No Middle | | | | | | | | | | | | | | | | | | | |
"nb", // No Bottom | | | | | | | | | | | | | | | | | | | |
"ro", // Range Only | X | | | | | | | | | | | | | | | | | X | |
"mo", // Melee Only | X | | | | | | | | | | | | | | | | X | | |
"sp" // Shuffle Players | | | | | | | | | | | | | | | | | | | |
};
// These options are always available
static char *Misc[] = {
"ns", // No Swap
"nr", // No Repick
"ts", // Terrain Snow
"pm", // Pooling Mode
"oi", // Observer Info
"mi", // Mini Heroes
"fr", // Fast Respawn
"so" // Switch On
};
Examples: Here are some example input, with the desired output:
“DotA v6.60 -RDSOSP USA/CA LC!” -> “rdsosp”
“DOTA AREMDM USA LC” -> “aremdm”
“DotA v6.60 -ApEmDuSpId USA BL” -> “apemduspid”
Notes: The solution doesn’t necessarily have to provide actual code, pseudo-code and even just an explanation of how you would handle it is acceptable and preferred. Also, the solution needs to be flexible enough to where I can add another game mode fairly easily. It is also safe to assume that within the game name, the game mode will always start with a primary game mode.
Result:
#include <cstdarg>
#include <algorithm>
#include <iostream>
#include <string>
#include <sstream>
#include <map>
#include <vector>
std::map<std::string, std::vector<std::string> > ModeCompatibilityMap;
static const unsigned int PrimaryModesCount = 11;
static char *PrimaryModes[] = {
"ap", "ar", "tr", "mr", "lm", "rd", "vr", "el", "sd", "cm", "cd"
};
static const unsigned int SecondaryModesCounts = 19;
static char *SecondaryModes[] = {
"dm", "rv", "mm", "du", "sh", "aa", "ai", "as", "id", "em", "np",
"sc", "om", "nt", "nm", "nb", "ro", "mo", "sp"
};
static const unsigned int MiscModesCount = 8;
static char *MiscModes[] = {
"ns", "nr", "ts", "pm", "oi", "mi", "fr", "so"
};
std::vector<std::string> Vectorize( int count, ... ) {
std::vector<std::string> result;
va_list vl;
va_start( vl, count );
for ( int i = 0; i < count; ++i ) {
char *buffer = va_arg( vl, char * );
result.push_back( buffer );
}
va_end( vl );
return result;
}
void InitializeModeCompatibilityMap() {
// Primary
ModeCompatibilityMap["ar"] = Vectorize( 1, "rv" );
ModeCompatibilityMap["tr"] = Vectorize( 2, "dm", "rv" );
ModeCompatibilityMap["mr"] = Vectorize( 8, "dm", "rv", "sh", "aa", "ai", "as", "ro", "mo" );
ModeCompatibilityMap["lm"] = Vectorize( 18, "dm", "rv", "mm", "du", "sh", "aa", "ai", "as", "id", "em", "np", "sc", "om", "nt", "nm", "nb", "ro", "mo" );
ModeCompatibilityMap["rd"] = Vectorize( 9, "dm", "rv", "mm", "sh", "aa", "ai", "as", "ro", "mo" );
ModeCompatibilityMap["vr"] = Vectorize( 9, "dm", "rv", "mm", "sh", "aa", "ai", "as", "ro", "mo" );
ModeCompatibilityMap["el"] = Vectorize( 18, "dm", "rv", "mm", "du", "sh", "aa", "ai", "as", "id", "em", "np", "sc", "om", "nt", "nm", "nb", "ro", "mo" );
ModeCompatibilityMap["sd"] = Vectorize( 9, "dm", "rv", "mm", "sh", "aa", "ai", "as", "ro", "mo" );
ModeCompatibilityMap["cm"] = Vectorize( 19, "dm", "rv", "mm", "du", "sh", "aa", "ai", "as", "id", "em", "np", "sc", "om", "nt", "nm", "nb", "ro", "mo", "sp" );
ModeCompatibilityMap["cd"] = Vectorize( 9, "dm", "rv", "mm", "sh", "aa", "ai", "as", "ro", "mo" );
// Secondary
ModeCompatibilityMap["dm"] = Vectorize( 8, "rv", "mm", "sh", "aa", "ai", "as", "ro", "mo" );
ModeCompatibilityMap["rv"] = Vectorize( 2, "dm", "sh" );
ModeCompatibilityMap["mm"] = Vectorize( 2, "dm", "sh" );
ModeCompatibilityMap["sh"] = Vectorize( 3, "dm", "rv", "mm" );
ModeCompatibilityMap["aa"] = Vectorize( 3, "dm", "ai", "as" );
ModeCompatibilityMap["ai"] = Vectorize( 3, "dm", "aa", "as" );
ModeCompatibilityMap["as"] = Vectorize( 3, "dm", "aa", "ai" );
ModeCompatibilityMap["ro"] = Vectorize( 2, "dm", "mo" );
ModeCompatibilityMap["mo"] = Vectorize( 2, "dm", "ro" );
}
std::vector<std::string> Tokenize( const std::string &string ) {
std::vector<std::string> tokens;
std::string token;
std::stringstream ss( string );
while ( ss >> token ) {
tokens.push_back( token );
}
return tokens;
}
void SanitizeString( std::string &in ) {
std::transform( in.begin(), in.end(), in.begin(), tolower );
for ( size_t i = 0; i < in.size(); ++i ) {
if ( in[i] < 'a' || in[i] > 'z' ) {
in.erase( i--, 1 );
}
}
}
std::vector<std::string> SplitString( const std::string &in, int count ) {
std::vector<std::string> result;
if ( in.length() % count != 0 ) {
return result;
}
for ( std::string::const_iterator i = in.begin(); i != in.end(); i += count ) {
result.push_back( std::string( i, i + count ) );
}
return result;
}
bool IsPrimaryGameMode( const std::string &in ) {
for ( int i = 0; i < PrimaryModesCount; ++i ) {
if ( strcmp( PrimaryModes[i], in.c_str() ) == 0 ) {
return true;
}
}
return false;
}
bool IsSecondaryGameMode( const std::string &in ) {
for ( int i = 0; i < SecondaryModesCounts; ++i ) {
if ( strcmp( SecondaryModes[i], in.c_str() ) == 0 ) {
return true;
}
}
return false;
}
bool IsMiscGameMode( const std::string &in ) {
for ( int i = 0; i < MiscModesCount; ++i ) {
if ( strcmp( MiscModes[i], in.c_str() ) == 0 ) {
return true;
}
}
return false;
}
bool IsValidGameMode( std::string in, std::string &out ) {
// 1. Strip all non-letters from the string and convert it to lower-case
SanitizeString( in );
// 2. Confirm that it is a multiple of 2
if ( in.length() == 0 || in.length() % 2 != 0 ) {
return false;
}
// 3. Split the string further into strings of 2 characters
std::vector<std::string> modes = SplitString( in, 2 );
// 4. Verify that each game mode is a valid game mode and is compatible with the others
bool primaryModeSet = false;
for ( size_t i = 0; i < modes.size(); ++i ) {
if ( IsPrimaryGameMode( modes[i] ) || IsSecondaryGameMode( modes[i] ) ) {
if ( IsPrimaryGameMode( modes[i] ) ) {
if ( primaryModeSet ) {
return false;
}
primaryModeSet = true;
}
if ( ModeCompatibilityMap.count( modes[i] ) > 0 ) {
std::vector<std::string> badModes = ModeCompatibilityMap[modes[i]];
for ( size_t j = 0; j < badModes.size(); ++j ) {
for ( size_t k = 0; k < modes.size(); ++k ) {
if ( badModes[j] == modes[k] ) {
return false;
}
}
}
}
} else if ( !IsMiscGameMode( modes[i] ) ) {
return false;
}
}
// 5. Assign the output variable with the game mode and return true
out = in;
return true;
}
std::string ExtractGameMode( const std::string &gameName ) {
std::vector<std::string> tokens = Tokenize( gameName );
std::string gameMode;
for ( size_t i = 0; i < tokens.size(); ++i ) {
if ( IsValidGameMode( tokens[i], gameMode ) ) {
return gameMode;
}
}
return "";
}
int main( int argc, char *argv[] ) {
InitializeModeCompatibilityMap();
std::string gameName = "DotA v6.60 -RDEM USA/CA LC";
std::string gameMode = ExtractGameMode( gameName );
std::cout << "Name: " << gameName << std::endl;
std::cout << "Mode: " << gameMode << std::endl;
return 0;
}
Output:
Name: DotA v6.60 -RDEM USA/CA LC
Mode: rdem
If anyone would like to review this code and let me know what they would change, that would be appreciated.
Thanks.
Extracting the game type from the host’s game name will be difficult without more rules in place. If you really want to avoid giving the end user more rules you could try the following…
That should extract the game type, or you can blame the user for using a bad game name.
Now for the harder part, verifying if the gametypes are compatible with each other. I suggest that you make a struct that holds a data structure of booleans representing each of the secondary game types. A std::map, or a single boolean array that could be accessed using a enum.
Now you’re going to need a data object to represent each primary game type as well as each secondary game type.
Then just make an array of every gametype, both primary and secondary. Refer to code sample:
As you see, there technically isn’t a difference between your primary and secondary game types, they both have the same restriction… may not be compatible with another secondary game type.
With this I’m sure you can figure out the rest. I hope it helps, I gotta get back to work 🙂
Oh and I’m a big fan of DotA… keep up the good work!