Files
wagic/projects/mtg/src/DeckStats.cpp
T
omegablast2002@yahoo.com eaf4d94a2e this part didnt commit in last rev, it fixes the mana display.
the progress is removed on this though, so you might not notice it until next release when you do a fresh install.
any new decks for both player or ai will show the mana symbols after a match is completed.
2013-01-27 19:35:52 +00:00

623 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;
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
float noLandsProbInTurn[Constants::STATS_FOR_TURNS] = {0.0f};
float noCreaturesProbInTurn[Constants::STATS_FOR_TURNS] = {0.0f};
int countCardsPerCostAndColor[Constants::STATS_MAX_MANA_COST + 1][Constants::MTG_NB_COLORS + 1] = {{0,0}};
int countCreaturesPerCostAndColor[Constants::STATS_MAX_MANA_COST + 1][Constants::MTG_NB_COLORS + 1] = {{0,0}};
int countSpellsPerCostAndColor[Constants::STATS_MAX_MANA_COST + 1][Constants::MTG_NB_COLORS + 1] = {{0,0}};
int countCardsPerCost[Constants::STATS_MAX_MANA_COST + 1] = {0};
int countCreaturesPerCost[Constants::STATS_MAX_MANA_COST + 1] = {0};
int countSpellsPerCost[Constants::STATS_MAX_MANA_COST + 1] = {0};
int countLandsPerColor[Constants::MTG_NB_COLORS + 1] = {0};
int countBasicLandsPerColor[Constants::MTG_NB_COLORS + 1] = {0};
int countNonLandProducersPerColor[Constants::MTG_NB_COLORS + 1] = {0};
int totalCostPerColor[Constants::MTG_NB_COLORS + 1] = {0};
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;
}
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 char * _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();
}