Status update on Outpost Monopoly refactoring,The typedefs have been removed. For data structures, I've made most of the short values into UINT8 values. The source code still compiles and runs fine with these changes. I'm still waffling on the UINT8 vs setting everything as INT. I have a poor handle on casting in C++ and I feel like making them all INT values keeps me out of trouble, but maybe not right answer.
For parsing out messages to initiate trades with other players, I decided to try transforming the char* pulled from the in game chat message into a std::string.
First is a helper function that splits a string into a vector<string> based on a delimiter. I couldn't find a standard library function to do this and didn't want to deal with adding Boost library dependency. If someone knows of a better way, I would be interested. Function is lifted from internet besides some light changes.
//https://www.safaribooksonline.com/library/view/c-cookbook/0596007612/ch04s07.html
void splitStringToVector(const string& stringToSplit, char delimiter, vector<string>& splitString)
{
string::size_type i = 0;
string::size_type j = stringToSplit.find(delimiter);
while (j != string::npos)
{
splitString.push_back(stringToSplit.substr(i, j - i));
i = ++j;
j = stringToSplit.find(delimiter, j);
if (j == string::npos)
{
splitString.push_back(stringToSplit.substr(i, stringToSplit.length()));
}
}
}
The Hook function checks to see if incoming chat messages are meant to be parsed and consumed . It then farms to message out to the appropriate function. I'm sending both the pointer to the chat text and the split string for parsing because the chat text is actually replaced by a new message and I need to pass a reference/pointer to do this.
void Hook(char* chatText, int sourcePlayerNum)
{
vector<string> splitString;
string chatString(chatText);
std::transform(chatString.begin(), chatString.end(), chatString.begin(), toupper);
splitStringToVector(chatString, ' ', splitString);
string command = splitString[0];
if (command.compare("/TRADE") == 0)
{
ParseTrade(chatText, sourcePlayerNum, splitString);
}
else if (command.compare("/PAY") == 0 && splitString[1].compare("JAIL") == 0)
{
ParsePayJail(chatText, sourcePlayerNum, splitString);
}
else if (command.compare("/KICK") == 0)
{
ParseKick(chatText, sourcePlayerNum, splitString);
}
else if (command.compare("/QUIT") == 0)
{
ParseQuit(chatText, sourcePlayerNum, splitString);
}
else if (command.compare("/START") == 0)
{
ParseStart(chatText, sourcePlayerNum, splitString);
}
else if (command.compare("/REVIEW") == 0)
{
ParseReview(chatText, sourcePlayerNum, splitString);
}
}
I'll just pull ParseTrade and then the ParseTradeGoOJF as an example, as it is too much code to show all the different parse functions here (I also haven't finished reformatting them all anyways). GoOJF stands for Get Out Of Jail Free. I'm not happy with this acronym in the code, but I don't have a better solution.
void ParseTrade(char* chatText, const int sourcePlayerNum, const vector<string>& splitString)
{
string tradeType = splitString[1];
if (tradeType.compare("LOT") == 0 || tradeType.compare("PROPERTY") || tradeType.compare("LAND"))
{
ParseTradeProperty(chatText, sourcePlayerNum, splitString);
}
else if (tradeType.compare("GOOJF") == 0 || tradeType.compare("JAIL"))
{
ParseTradeGoOJF(chatText, sourcePlayerNum, splitString);
}
else if (tradeType.compare("ORE") == 0 || tradeType.compare("MONEY") || IsNumeric(tradeType))
{
ParseTradeOre(chatText, sourcePlayerNum, splitString);
}
else
{
strcpy(chatText, "Unrecognized trade command.");
TethysGame::AddGameSound(sndBld_not, -1);
}
}
Below are helper functions dealing with parsing the message.
I'm not a fan of the format of functions like
bool FindPlayersGoOJFCard(const int playerIndex, int& cardIndex). The other ways I know to solve this would be to use try/catch or set the index to -1 if the parse doesn't work out. I'm not really a fan of any of these solutions though, so I just went with setting the value and returning a bool if it is valid value or not.
bool IsNumeric(const std::string& input) {
return std::all_of(input.begin(), input.end(), ::isdigit);
}
bool ParsePlayerIndex(char* chatText, const string& inputString, int& outputInt)
{
if (!IsNumeric(inputString))
{
FormatInputNotNumberResponse(chatText);
return false;
}
outputInt = stoi(inputString);
if (!IsValidPlayerIndex(outputInt))
{
FormatPlayerDoesNotExistResponse(chatText);
return false;
}
return true;
}
bool PlayerActive(const int playerIndex)
{
for (int i = 0; i < numberActivePlayers; i++)
{
if (MonopolyPlayers[i].TurnIndex == playerIndex - 1)
{
return true;
}
}
return false;
}
bool FindPlayersGoOJFCard(const int playerIndex, int& cardIndex)
{
for (int i = 2; i >= 0; i--) //Find an owned GOoJF card
{
if (GOOJF[i] == playerIndex) //Make sure it's owned by the sender
{
cardIndex = i;
return true;
}
}
return false;
}
Below is the meat of parsing trade requests for GoOJF cards. Trading Ore (money) and properties would be formatted the same way.
void ParseTradeGoOJF(char* chatText, const int sourcePlayerIndex, const vector<string> splitString)
{
int receivingPlayerIndex;
if (!ParsePlayerIndex(chatText, splitString[2], receivingPlayerIndex))
{
return;
}
if (sourcePlayerIndex == receivingPlayerIndex)
{
FormatTradeWithSelfResponse(chatText);
return;
}
if (!PlayerActive(receivingPlayerIndex))
{
FormatPlayerNoLongerActive(chatText);
return;
}
int goojfIndex;
if (!FindPlayersGoOJFCard(sourcePlayerIndex, goojfIndex))
{
FormatDoNotOwnGetOutOfJailFreeCard(chatText);
return;
}
TradeGOoJF(goojfIndex, receivingPlayerIndex, sourcePlayerIndex, chatText);
}
Okay, if you made it through all of that, I am interested in thoughts/critiques before implementing this all over the code base. Once I nail down the message parsing better, I'll plan to commit what I have back to the repository.
Random other question: is there a preferred guideline for function name capitalization? I'm used to writing the first letter of a function as capital, like ParsePlayerIndex but all the standard library functions in c++ start with lowercase, like stoi or atoi.