added
"whenever a creature enters the battlefield you may pay {1}, if you do gain one life"
conditional may pay({cost}) effect
this version is a super type ability, and can only be used in certain combos.
to nest you will need to use it in its subtype pay[[{cost}]] effect
pay keyword can have sideeffects coded as follows
pay[[{1}]] life:1?life:-1
pay one mana and gain 1 life, if you dont then you lose one life. notice no space between the abilities and the question mark.
added castcard()
a method to cast a targeted card, this contains the following subkeywords which can be used in combinations
(normal)
(restricted)
(copied)
(noevent)
castcard(restricted copied noevent) for example will cast a card that is a copy or the spell without sending a cast event only when the spell is castable.
"normal" subkeyword cast the actual spell, not a copy.
extended the use of exiledeath to everyzone, any card going from any zone to graveyard is placed in exile if it has exiledeath.
limited swipe left to open hand only when hand is closed view.
"moveto(" can now be named.
624 lines
20 KiB
C++
624 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
|
|
|
|
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();
|
|
}
|
|
|