Files
wagic/projects/mtg/src/DeckStats.cpp
2015-11-10 11:58:36 +08:00

625 lines
20 KiB
C++

#include "PrecompiledHeader.h"
#include "DeckManager.h"
#include "DeckStats.h"
#include "Player.h"
#include "GameObserver.h"
#include "MTGDeck.h"
#include "ManaCostHybrid.h"
DeckStats * DeckStats::mInstance = NULL;
DeckStat::DeckStat(int nbgames, int victories, string manaColorIndex) : nbgames(nbgames), victories(victories), manaColorIndex( manaColorIndex)
{
}
int DeckStat::percentVictories()
{
if (nbgames == 0) return 50;
return (100 * victories / nbgames);
}
DeckStats * DeckStats::GetInstance()
{
if (!mInstance)
{
mInstance = NEW DeckStats();
}
return mInstance;
}
DeckStats::~DeckStats()
{
map<string, map<string,DeckStat*> > ::iterator it;
for (it = masterDeckStats.begin(); it != masterDeckStats.end(); ++it)
{
string key = it->first;
map<string, DeckStat *> innerMap = masterDeckStats[key];
map<string, DeckStat *>::iterator it2;
for (it2 = innerMap.begin(); it2 != innerMap.end(); it2++)
{
SAFE_DELETE(it2->second);
}
innerMap.clear();
}
masterDeckStats.clear();
}
DeckStat* DeckStats::getDeckStat(string opponentsFile)
{
map<string, DeckStat *> stats = masterDeckStats[currentDeck];
//map<string, DeckStat *>::iterator it = stats.find(opponentsFile);
//this method can not find the opponentfile string
//stats string for first doesn't even act like a string, i was forced to pull it out of the
//iter to make the comparison. it->first.find( for example was not finding it even though i can
//see them in the debugger as matching strings
map<string, DeckStat *>::iterator it;
for (it = stats.begin(); it != stats.end(); ++it)
{
string deckStatName = it->first;
if(deckStatName.find(opponentsFile.c_str()) != string::npos)
{
break;
}
}
if (it == stats.end())
{
return NULL;
}
else
{
return it->second;
}
}
int DeckStats::nbGames()
{
int nbgames = 0;
map<string, DeckStat *> stats = masterDeckStats[currentDeck];
map<string, DeckStat *>::iterator it;
for (it = stats.begin(); it != stats.end(); it++)
{
DeckStat * d = it->second;
nbgames += d->nbgames;
}
return nbgames;
}
int DeckStats::percentVictories(string opponentsFile)
{
map<string, DeckStat *> stats = masterDeckStats[currentDeck];
map<string, DeckStat *>::iterator it = stats.find(opponentsFile);
if (it == stats.end())
{
return 50;
}
else
{
return (it->second->percentVictories());
}
}
int DeckStats::percentVictories()
{
int victories = 0;
int nbgames = 0;
map<string, DeckStat *> stats = masterDeckStats[currentDeck];
map<string, DeckStat *>::iterator it;
for (it = stats.begin(); it != stats.end(); it++)
{
DeckStat * d = it->second;
nbgames += d->nbgames;
victories += d->victories;
}
if (nbgames)
{
return (victories * 100) / nbgames;
}
return 50;
}
void DeckStats::load(const std::string& filename)
{
currentDeck = filename;
if ( masterDeckStats.find(filename) != masterDeckStats.end() )
{
return;
}
std::string contents;
if (JFileSystem::GetInstance()->readIntoString(filename, contents))
{
std::stringstream stream(contents);
std::string s;
// get the associated player deck file:
int deckId = atoi(filename.substr(filename.find("_deck") + 5, filename.find(".txt")).c_str());
char buffer[512];
sprintf(buffer, "deck%i.txt", deckId);
string playerDeckFilePath = options.profileFile( buffer);
DeckMetaData *playerDeckMetaData = DeckManager::GetInstance()->getDeckMetaDataByFilename( playerDeckFilePath, false);
if (!playerDeckMetaData)
{
DebugTrace("DeckStats.cpp:CONSISTENCY ERROR: DeckStats are set, but no deck meta data");
return;
}
// check if this player deck has already been profiled for manacolors
char next = stream.peek();
string manaColorIndex = "";
if ( next == 'M')
{
std::getline(stream, s );
manaColorIndex = s.substr( s.find(":") + 1);
playerDeckMetaData->setColorIndex( manaColorIndex );
}
while (std::getline(stream, s))
{
string deckfile = s;
std::getline(stream, s);
int games = atoi(s.c_str());
std::getline(stream, s);
int victories = atoi(s.c_str());
next = stream.peek();
if ( next == 'M')
{
std::getline(stream, s );
manaColorIndex = s.substr( s.find(":") + 1);
}
if ( masterDeckStats[filename].find(deckfile) != masterDeckStats[filename].end())
{
SAFE_DELETE( masterDeckStats[filename][deckfile] );
}
DeckStat * newDeckStat = NEW DeckStat(games, victories, manaColorIndex);
(masterDeckStats[filename])[deckfile] = newDeckStat;
}
}
}
void DeckStats::save(const std::string& filename)
{
std::ofstream file;
if (JFileSystem::GetInstance()->openForWrite(file, filename))
{
char writer[512];
map<string, DeckStat *> stats = masterDeckStats[currentDeck];
map<string, DeckStat *>::iterator it;
string manaColorIndex = "";
int deckId = atoi(filename.substr(filename.find("_deck") + 5, filename.find(".txt")).c_str());
char buffer[512];
sprintf(buffer, "deck%i.txt", deckId);
string playerDeckFilePath= options.profileFile( buffer);
DeckManager *deckManager = DeckManager::GetInstance();
DeckMetaData *playerDeckMeta = deckManager->getDeckMetaDataByFilename(playerDeckFilePath, false);
if (playerDeckMeta && playerDeckMeta->getColorIndex() == "" )
{
StatsWrapper *stw = deckManager->getExtendedDeckStats( playerDeckMeta, MTGAllCards::getInstance(), false);
manaColorIndex = stw->getManaColorIndex();
playerDeckMeta->setColorIndex( manaColorIndex );
}
file << "MANA:" << manaColorIndex << endl;
if(file)
for (it = stats.begin(); it != stats.end(); it++)
{
sprintf(writer, "%s\n", it->first.c_str());
file << writer;
sprintf(writer, "%i\n", it->second->nbgames);
file << writer;
sprintf(writer, "%i\n", it->second->victories);
file << writer;
file << "MANA:" << it->second->manaColorIndex <<endl;
}
file.close();
if(playerDeckMeta)
playerDeckMeta->Invalidate();
}
}
void DeckStats::saveStats(Player *player, Player *opponent, GameObserver * game)
{
int victory = 1;
if (!game->didWin())
{
if (player->life == opponent->life) return;
if (player->life < opponent->life) victory = 0;
}
else if (!game->didWin(player))
{
victory = 0;
}
load(currentDeck);
map<string, DeckStat *> *stats = &masterDeckStats[currentDeck];
map<string, DeckStat *>::iterator it = stats->find(opponent->deckFileSmall);
string manaColorIndex = "";
DeckManager *deckManager = DeckManager::GetInstance();
DeckMetaData *aiDeckMeta = deckManager->getDeckMetaDataByFilename( opponent->deckFile, true);
StatsWrapper *stw = deckManager->getExtendedDeckStats( aiDeckMeta, MTGAllCards::getInstance(), true);
manaColorIndex = stw->getManaColorIndex();
if (it == stats->end())
{
stats->insert( make_pair( opponent->deckFileSmall, NEW DeckStat(1, victory, manaColorIndex) ));
}
else
{
it->second->victories += victory;
it->second->nbgames += 1;
if ( it->second->manaColorIndex == "" )
{
it->second->manaColorIndex = manaColorIndex;
}
}
save(currentDeck);
DeckMetaData* playerMeta = DeckManager::GetInstance()->getDeckMetaDataByFilename(player->deckFile, false);
// metadata caches its internal data (number of games, victories, etc)
// tell it to refresh when stats are updated
if (playerMeta)
playerMeta->Invalidate();
DeckMetaData* aiMeta = DeckManager::GetInstance()->getDeckMetaDataByFilename(opponent->deckFile, true);
if (aiMeta)
aiMeta->Invalidate();
}
void DeckStats::EndInstance()
{
SAFE_DELETE( mInstance );
}
// StatsWrapper
void StatsWrapper::initValues()
{
// initilize all member values to 0
// Stats parameters and status
mDeckId = currentPage = pageCount = 0;
needUpdate = true;
// Actual stats
percentVictories = 0;
gamesPlayed = cardCount = countLands = totalPrice = totalManaCost = 0;
totalCreatureCost = totalSpellCost = countManaProducers = 0;
avgManaCost = avgCreatureCost = avgSpellCost = 0.0f;
countCreatures = countSpells = countInstants = countEnchantments = countSorceries = countArtifacts = 0;
//this works only with 0.0f on floats
memset(noLandsProbInTurn, (int)0.0f, sizeof(float) * Constants::STATS_FOR_TURNS);
memset(noCreaturesProbInTurn, (int)0.0f, sizeof(float) * Constants::STATS_FOR_TURNS);
memset(countCardsPerCost, 0, sizeof(int) * (Constants::STATS_MAX_MANA_COST + 1));
memset(countCreaturesPerCost, 0, sizeof(int) * (Constants::STATS_MAX_MANA_COST + 1));
memset(countSpellsPerCost, 0, sizeof(int) * (Constants::STATS_MAX_MANA_COST + 1));
memset(countCardsPerCostAndColor, 0, sizeof(int) * (Constants::STATS_MAX_MANA_COST + 1)*(Constants::MTG_NB_COLORS + 1));
memset(countCreaturesPerCostAndColor, 0, sizeof(int) * (Constants::STATS_MAX_MANA_COST + 1)*(Constants::MTG_NB_COLORS + 1));
memset(countSpellsPerCostAndColor, 0, sizeof(int) * (Constants::STATS_MAX_MANA_COST + 1)*(Constants::MTG_NB_COLORS + 1));
memset(countLandsPerColor, 0, sizeof(int) * (Constants::MTG_NB_COLORS + 1));
memset(countBasicLandsPerColor, 0, sizeof(int) * (Constants::MTG_NB_COLORS + 1));
memset(countNonLandProducersPerColor, 0, sizeof(int) * (Constants::MTG_NB_COLORS + 1));
memset(totalCostPerColor, 0, sizeof(int) * (Constants::MTG_NB_COLORS + 1));
}
StatsWrapper::StatsWrapper(int deckId)
{
mDeckId = deckId;
char buffer[512];
sprintf(buffer, "stats/player_deck%i.txt", deckId);
string deckstats = options.profileFile(buffer);
initStatistics(deckstats);
}
StatsWrapper::StatsWrapper(string deckstats)
{
initStatistics(deckstats);
}
void StatsWrapper::initStatistics(string deckstats)
{
// initialize member variables to make sure they have valid values
initValues();
// Load deck statistics
DeckStats * stats = DeckStats::GetInstance();
aiDeckNames.clear();
aiDeckStats.clear();
if (FileExists(deckstats))
{
stats->load(deckstats.c_str());
percentVictories = stats->percentVictories();
gamesPlayed = stats->nbGames();
// Detailed deck statistics against AI
int nbDecks = 0;
char buffer[512];
char smallDeckName[512];
ostringstream oss;
oss << "deck" << (nbDecks + 1);
string bakaDir = "ai/baka/";
string deckFilename = oss.str();
sprintf(buffer, "%s/%s.txt", bakaDir.c_str(), deckFilename.c_str());
if (fileExists(buffer))
{
nbDecks++;
sprintf(smallDeckName, "%s_deck%i", "ai_baka", nbDecks);
DeckStat* deckStat = stats->getDeckStat(string(smallDeckName));
if ((deckStat != NULL) && (deckStat->nbgames > 0))
{
aiDeckNames.push_back(deckFilename);
aiDeckStats.push_back(deckStat);
}
}
}
else
{
gamesPlayed = 0;
percentVictories = 0;
}
}
string StatsWrapper::getManaColorIndex()
{
ostringstream oss;
for (int i = Constants::MTG_COLOR_ARTIFACT; i < Constants::MTG_COLOR_LAND; ++i)
if (totalCostPerColor[i] != 0)
oss << "1";
else
oss <<"0";
string index = oss.str();
return index;
}
void StatsWrapper::updateStats(string filename, MTGAllCards *collection)
{
if (FileExists(filename))
{
MTGDeck * mtgd = NEW MTGDeck(filename.c_str(), collection);
DeckDataWrapper *deckDataWrapper = NEW DeckDataWrapper(mtgd);
updateStats(deckDataWrapper);
SAFE_DELETE( mtgd );
SAFE_DELETE( deckDataWrapper );
}
}
void StatsWrapper::updateStats(DeckDataWrapper *myDeck)
{
if (!this->needUpdate || !myDeck) return;
this->needUpdate = false;
this->cardCount = myDeck->getCount(WSrcDeck::UNFILTERED_COPIES);
this->countLands = myDeck->getCount(Constants::MTG_COLOR_LAND);
this->totalPrice = myDeck->totalPrice();
this->countManaProducers = 0;
// Mana cost
int currentCount, convertedCost;
ManaCost * currentCost;
this->totalManaCost = 0;
this->totalCreatureCost = 0;
this->totalSpellCost = 0;
MTGCard * current = NULL;
// Clearing arrays
for (int i = 0; i <= Constants::STATS_MAX_MANA_COST; i++)
{
this->countCardsPerCost[i] = 0;
this->countCreaturesPerCost[i] = 0;
this->countSpellsPerCost[i] = 0;
}
for (int i = 0; i <= Constants::NB_Colors; i++)
{
this->totalCostPerColor[i] = 0;
this->countLandsPerColor[i] = 0;
this->countBasicLandsPerColor[i] = 0;
this->countNonLandProducersPerColor[i] = 0;
}
for (int i = 0; i <= Constants::STATS_MAX_MANA_COST; i++)
{
for (int k = 0; k <= Constants::NB_Colors; k++)
{
this->countCardsPerCostAndColor[i][k] = 0;
this->countCreaturesPerCostAndColor[i][k] = 0;
this->countSpellsPerCostAndColor[i][k] = 0;
}
}
for (int ic = 0; ic < myDeck->Size(true); ic++)
{
current = myDeck->getCard(ic, true);
currentCost = current->data->getManaCost();
convertedCost = currentCost->getConvertedCost();
currentCount = myDeck->count(current);
// Add to the cards per cost counters
this->totalManaCost += convertedCost * currentCount;
if (convertedCost > Constants::STATS_MAX_MANA_COST)
{
convertedCost = Constants::STATS_MAX_MANA_COST;
}
this->countCardsPerCost[convertedCost] += currentCount;
if (current->data->isCreature())
{
this->countCreaturesPerCost[convertedCost] += currentCount;
this->totalCreatureCost += convertedCost * currentCount;
}
else if (current->data->isSpell())
{
this->countSpellsPerCost[convertedCost] += currentCount;
this->totalSpellCost += convertedCost * currentCount;
}
// Lets look for mana producing abilities
//http://code.google.com/p/wagic/issues/detail?id=650
//Basic lands are not producing their mana through regular abilities anymore,
//but through a rule that is outside of the primitives. This block is a hack to address this
const int colors[] = {Constants::MTG_COLOR_GREEN, Constants::MTG_COLOR_BLUE, Constants::MTG_COLOR_RED, Constants::MTG_COLOR_BLACK, Constants::MTG_COLOR_WHITE};
const string lands[] = { "forest", "island", "mountain", "swamp", "plains" };
for (unsigned int i = 0; i < sizeof(colors)/sizeof(colors[0]); ++i)
{
int colorId = colors[i];
string type = lands[i];
if (current->data->hasType(type.c_str()))
{
if (current->data->hasType("Basic"))
{
this->countBasicLandsPerColor[colorId] += currentCount;
}
else
{
this->countLandsPerColor[colorId] += currentCount;
}
}
}
vector<string> abilitiesVector;
string thisstring = current->data->magicText;
abilitiesVector = split(thisstring, '\n');
for (int v = 0; v < (int) abilitiesVector.size(); v++)
{
string s = abilitiesVector[v];
size_t t = s.find("add");
if (t != string::npos)
{
s = s.substr(t + 3);
ManaCost * mc = ManaCost::parseManaCost(s);
for (int j = 0; j < Constants::NB_Colors; j++)
{
if (mc->hasColor(j))
{
if (current->data->isLand())
{
if (current->data->hasType("Basic"))
{
this->countBasicLandsPerColor[j] += currentCount;
}
else
{
this->countLandsPerColor[j] += currentCount;
}
}
else
{
this->countNonLandProducersPerColor[j] += currentCount;
}
}
}
SAFE_DELETE(mc);
}
}
// Add to the per color counters
// a. regular costs
for (int j = 0; j < Constants::NB_Colors; j++)
{
this->totalCostPerColor[j] += currentCost->getCost(j) * currentCount;
if (current->data->hasColor(j))
{
// Add to the per cost and color counter
this->countCardsPerCostAndColor[convertedCost][j] += currentCount;
if (current->data->isCreature())
{
this->countCreaturesPerCostAndColor[convertedCost][j] += currentCount;
}
else if (current->data->isSpell())
{
this->countSpellsPerCostAndColor[convertedCost][j] += currentCount;
}
}
}
// b. Hybrid costs
ManaCostHybrid * hybridCost;
int i;
i = 0;
while ((hybridCost = currentCost->getHybridCost(i++)) != NULL)
{
this->totalCostPerColor[hybridCost->color1] += hybridCost->value1 * currentCount;
this->totalCostPerColor[hybridCost->color2] += hybridCost->value2 * currentCount;
}
}
this->totalColoredSymbols = 0;
for (int j = 1; j < Constants::NB_Colors; j++)
{
this->totalColoredSymbols += this->totalCostPerColor[j];
}
this->countCardsPerCost[0] -= this->countLands;
// Counts by type
this->countCreatures = countCardsByType("Creature", myDeck);
this->countInstants = countCardsByType("Instant", myDeck);
this->countEnchantments = countCardsByType("Enchantment", myDeck);
this->countSorceries = countCardsByType("Sorcery", myDeck);
this->countSpells = this->countInstants + this->countEnchantments + this->countSorceries;
//this->countArtifacts = countCardsByType("Artifact", myDeck);
// Average mana costs
this->avgManaCost = ((this->cardCount - this->countLands) <= 0) ? 0 : (float) this->totalManaCost / (this->cardCount
- this->countLands);
this->avgCreatureCost = (this->countCreatures <= 0) ? 0 : (float) this->totalCreatureCost / this->countCreatures;
this->avgSpellCost = (this->countSpells <= 0) ? 0 : (float) this->totalSpellCost / this->countSpells;
// Probabilities
// TODO: this could be optimized by reusing results
for (int i = 0; i < Constants::STATS_FOR_TURNS; i++)
{
this->noLandsProbInTurn[i] = noLuck(this->cardCount, this->countLands, 7 + i) * 100;
this->noCreaturesProbInTurn[i] = noLuck(this->cardCount, this->countCreatures, 7 + i) * 100;
}
}
// This should probably be cached in DeckDataWrapper
// or at least be calculated for all common types in one go
int StatsWrapper::countCardsByType(const string& _type, DeckDataWrapper * myDeck)
{
int result = 0;
for (int i = 0; i < myDeck->Size(true); i++)
{
MTGCard * current = myDeck->getCard(i, true);
if (current->data->hasType(_type))
{
result += myDeck->count(current);
}
}
return result;
}
// n cards total, a of them are of desired type (A), x drawn
// returns probability of no A's
float StatsWrapper::noLuck(int n, int a, int x)
{
if ((a >= n) || (a == 0)) return 1;
if ((n == 0) || (x == 0) || (x > n) || (n - a < x)) return 0;
a = n - a;
float result = 1;
for (int i = 0; i < x; i++)
result *= (float) (a - i) / (n - i);
return result;
}
StatsWrapper::~StatsWrapper()
{
aiDeckNames.clear();
aiDeckStats.clear();
}