//WCFilterProducesColor bool WCFilterProducesColor::isMatch(MTGCard * c) { bool bMatch = false; if (!c || !c->data) return false; //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 string lands[] = { "dummy(colorless)", "forest", "island", "mountain", "swamp", "plains" }; if ((color < (int)(sizeof(lands)/sizeof(lands[0]))) && c->data->hasType(lands[color].c_str())) return true; //Retrieve non basic Mana abilities string s = c->data->magicText; size_t t = s.find("add"); while (t != string::npos) { s = s.substr(t + 3); ManaCost * mc = ManaCost::parseManaCost(s); if (mc->hasColor(color) > 0) { bMatch = true; SAFE_DELETE(mc); break; } SAFE_DELETE(mc); t = s.find("add"); } return bMatch; }
int Spell::computeX(MTGCardInstance * card) { ManaCost * c = cost->Diff(card->getManaCost()); int x = c->getCost(Constants::NB_Colors); delete c; return x; }
string AIHints::constraintsNotFulfilled(AIAction * action, AIHint * hint, ManaCost * potentialMana) { std::stringstream out; if (!action) { if (hint->mCombatAttackTip.size()) { out << "to see if this can attack[" << hint->mCombatAttackTip << "]"; return out.str(); } if (hint->mSourceId && !findSource(hint->mSourceId)) { out << "needcardinplay[" << hint->mSourceId << "]"; return out.str(); } out << "needability[" << hint->mAction << "]"; return out.str(); } MTGAbility * a = action->ability; if (!a) return "not supported"; MTGCardInstance * card = action->click; if (!card) return "not supported"; //dummy test: would the ability work if we were sure to fulfill its mana requirements? if (!a->isReactingToClick(card, a->getCost())) { DebugTrace("This shouldn't happen, this AIAction doesn't seem like a good choice"); return "not supported"; } if (!a->isReactingToClick(card, potentialMana)) { //Not enough Mana, try to find which mana we should get in priority ManaCost * diff = potentialMana->Diff(a->getCost()); for (int i = 0; i < Constants::NB_Colors; i++) { if(diff->getCost(i) < 0) { out << "needmana[" << Constants::MTGColorChars[i] << "]"; if (Constants::MTGColorChars[i] == 'r') DebugTrace("Got it"); SAFE_DELETE(diff); return out.str(); } } //TODO, handle more cases where the cost cannot be paid return "not supported, can't afford cost for some reason"; } //No problem found, we believe this is a good action to perform return ""; }
int AIMomirPlayer::getEfficiency(OrderedAIAction * action) { MTGAbility * ability = action->ability; ManaCost * cost = ability->getCost(); if (cost && !(cost->isExtraPaymentSet())) return 0; //Does not handle abilities with sacrifice yet int efficiency = AIPlayerBaka::getEfficiency(action); if (observer->getCurrentGamePhase() < MTG_PHASE_FIRSTMAIN) return 0; return efficiency; }
//return 1 if _cost can be paid with current data, 0 otherwise int ManaCost::canAfford(ManaCost * _cost) { ManaCost * diff = Diff(_cost); int positive = diff->isPositive(); delete diff; if (positive) { return 1; } return 0; }
int ManaCost::pay(ManaCost * _cost) { int result = MANA_PAID; ManaCost * toPay = NEW ManaCost(); toPay->copy(_cost); ManaCost * diff = Diff(toPay); for (int i = 0; i < Constants::NB_Colors; i++) { cost[i] = diff->getCost(i); } delete diff; delete toPay; return result; //TODO return 0 if can't afford the cost! }
QString toString(const QVariant& data) { if (data.canConvert<ManaCost>()) { ManaCost manaCost = qvariant_cast<ManaCost>(data); return manaCost.getText(); } else if (data.canConvert<QStringList>()) { return data.toStringList().join(" / "); } else { return data.toString(); } }
int AIMomirPlayer::momir() { if (!game->hand->nb_cards) return 0; //nothing to discard :/ int result = 0; int opponentCreatures = getCreaturesInfo(opponent(), INFO_NBCREATURES); int myCreatures = getCreaturesInfo(this, INFO_NBCREATURES ); ManaCost * potentialMana = getPotentialMana(); int converted = potentialMana->getConvertedCost(); SAFE_DELETE(potentialMana); int efficiency = 100; int chance = 1 + (randomGenerator.random() % 100); if (converted == 5 && myCreatures > opponentCreatures && game->hand->nb_cards < 4) efficiency = 5; //Strategy: skip 5 drop if (converted == 7 && myCreatures > opponentCreatures && game->hand->nb_cards < 2) efficiency = 50; //Strategy: 7 drops have bad upkeep costs and the AI doesn't handle those right now... if (converted > 8) converted = 8; if (converted == 8) efficiency = 100 - (myCreatures - opponentCreatures); if (efficiency >= chance) { std::vector<int16_t> _cost; _cost.push_back(Constants::MTG_COLOR_ARTIFACT); _cost.push_back(converted); ManaCost * cost = NEW ManaCost(_cost); MTGAbility * ability = getMomirAbility(); MTGCardInstance * card = game->hand->cards[0]; if (ability->isReactingToClick(card, cost)) { payTheManaCost(cost); AIAction * a = NEW AIAction(this, ability, card); clickstream.push(a); result = 1; } delete cost; } return result; }
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; } }
ManaCost * ManaCost::parseManaCost(string s, ManaCost * _manaCost, MTGCardInstance * c) { ManaCost * manaCost; GameObserver* g = c?c->getObserver():NULL; if (_manaCost) { manaCost = _manaCost; } else { manaCost = NEW ManaCost(); } manaCost->xColor = -1; int state = 0; size_t start = 0; size_t end = 0; while (!s.empty() && state != -1) { switch (state) { case 0: start = s.find_first_of("{"); if(s.find_first_of("{") != string::npos && start > 0) { string value = s.substr(start -1,end); if(value == "n{")//"restrictio n{m orbid} would read the n{m as {m} millcost return manaCost; } if (start == string::npos) { return manaCost; } else { state = 1; } break; case 1: end = s.find_first_of("}"); if (end == string::npos) { state = -1; } else { string value = s.substr(start + 1, end - 1 - start); if (value == "u") { manaCost->add(Constants::MTG_COLOR_BLUE, 1); } else if (value == "b") { manaCost->add(Constants::MTG_COLOR_BLACK, 1); } else if (value == "w") { manaCost->add(Constants::MTG_COLOR_WHITE, 1); } else if (value == "g") { manaCost->add(Constants::MTG_COLOR_GREEN, 1); } else if (value == "r") { manaCost->add(Constants::MTG_COLOR_RED, 1); } else { //Parse target for extraCosts TargetChooserFactory tcf(g); TargetChooser * tc = NULL; size_t target_start = value.find("("); size_t target_end = value.find(")"); if (target_start != string::npos && target_end != string::npos) { string target = value.substr(target_start + 1, target_end - 1 - target_start); tc = tcf.createTargetChooser(target, c); } //switch on the first letter. If two costs share their first letter, add an "if" within the switch std::transform(value.begin(), value.end(), value.begin(), ::tolower); switch (value[0]) { case 'x': if(value == "x") { manaCost->x(); } else { vector<string>colorSplit = parseBetween(value,"x:"," ",false); if(colorSplit.size()) { int color = -1; const string ColorStrings[] = { Constants::kManaColorless, Constants::kManaGreen, Constants::kManaBlue, Constants::kManaRed, Constants::kManaBlack, Constants::kManaWhite }; for (unsigned int i = 0; i < sizeof(ColorStrings)/sizeof(ColorStrings[0]); ++i) { if (s.find(ColorStrings[i]) != string::npos) { color = i; } } manaCost->specificX(color); } } break; case 'v': if (value.find("value:") != string::npos) { vector<string> splitParsedVar = parseBetween(value, "value:", " ", false); WParsedInt* res = NEW WParsedInt(splitParsedVar[1], NULL, c); manaCost->add(Constants::MTG_COLOR_ARTIFACT, res->getValue()); SAFE_DELETE(res); } break; case 't': //Tap if (value == "t") { manaCost->addExtraCost(NEW TapCost); } else { manaCost->addExtraCost(NEW TapTargetCost(tc)); } break; case 's': if (value.find("s2l") != string::npos) { //Send To Library Cost (move from anywhere to Library) manaCost->addExtraCost(NEW ToLibraryCost(tc)); } else if (value.find("s2g") != string::npos) { //Send to Graveyard Cost (move from anywhere to Graveyard) manaCost->addExtraCost(NEW ToGraveCost(tc)); } else { //Sacrifice manaCost->addExtraCost(NEW SacrificeCost(tc)); } break; case 'e': //Exile manaCost->addExtraCost(NEW ExileTargetCost(tc)); break; case 'h': //bounce (move to Hand) manaCost->addExtraCost(NEW BounceTargetCost(tc)); break; case 'l': if (value == "l2e") { //Mill to exile yourself as a cost (Library 2 Exile) manaCost->addExtraCost(NEW MillExileCost(tc)); } else if (value == "l") { //Life cost manaCost->addExtraCost(NEW LifeCost(tc)); } else { //Specific Life cost vector<string>valSplit = parseBetween(value,"l:"," ",false); if (valSplit.size()) { WParsedInt* lifetopay = NEW WParsedInt(valSplit[1], NULL, c); manaCost->addExtraCost(NEW SpecificLifeCost(tc,lifetopay->getValue())); SAFE_DELETE(lifetopay); } } break; case 'd': //DiscardRandom cost if (value.find("delve") != string::npos) { if(!tc) tc = tcf.createTargetChooser("*|mygraveyard", c); manaCost->addExtraCost(NEW Delve(tc)); } else if (value == "d") { manaCost->addExtraCost(NEW DiscardRandomCost(tc)); } else { manaCost->addExtraCost(NEW DiscardCost(tc)); } break; case 'm': //Mill yourself as a cost manaCost->addExtraCost(NEW MillCost(tc)); break; case 'n': //return unblocked attacker cost { TargetChooserFactory tcf(g); tc = tcf.createTargetChooser("creature|myBattlefield", c); manaCost->addExtraCost(NEW Ninja(tc)); break; } case 'k': //kill offering { TargetChooserFactory tcf(g); if (value == "kgoblin") { tc = tcf.createTargetChooser("creature[goblin]|myBattlefield", c); } else if (value == "kfox") { tc = tcf.createTargetChooser("creature[fox]|myBattlefield", c); } else if (value == "kmoonfolk") { tc = tcf.createTargetChooser("creature[moonfolk]|myBattlefield", c); } else if (value == "krat") { tc = tcf.createTargetChooser("creature[rat]|myBattlefield", c); } else if (value == "ksnake") { tc = tcf.createTargetChooser("creature[snake]|myBattlefield", c); } //TODO iterate subtypes of creatures manaCost->addExtraCost(NEW Offering(tc)); break; } case 'p' : { SAFE_DELETE(tc); size_t start = value.find("("); size_t end = value.rfind(")"); string manaType = value.substr(start + 1, end - start - 1); manaCost->addExtraCost(NEW LifeorManaCost(NULL,manaType)); break; } case 'i' : { SAFE_DELETE(tc); manaCost->add(0,1); manaCost->addExtraCost(NEW SnowCost); break; } case 'q': if(value == "q") { manaCost->addExtraCost(NEW UnTapCost); } else { manaCost->addExtraCost(NEW UnTapTargetCost(tc)); } break; case 'c': //Counters or cycle { if (value.find("convoke") != string::npos) { if (!tc) tc = tcf.createTargetChooser("creature|mybattlefield", c); manaCost->addExtraCost(NEW Convoke(tc)); } else if(value == "chosencolor") { if(c) manaCost->add(c->chooseacolor, 1); } else if(value == "cycle") { manaCost->addExtraCost(NEW CycleCost(tc)); } else if(value.find("(") != string::npos) { size_t counter_start = value.find("("); size_t counter_end = value.find(")", counter_start); AbilityFactory abf(g); string counterString = value.substr(counter_start + 1, counter_end - counter_start - 1); Counter * counter = abf.parseCounter(counterString, c); size_t separator = value.find(",", counter_start); size_t separator2 = string::npos; if (separator != string::npos) { separator2 = value.find(",", counter_end + 1); } SAFE_DELETE(tc); size_t target_start = string::npos; if (separator2 != string::npos) { target_start = value.find(",", counter_end + 1); } size_t target_end = value.length(); if (target_start != string::npos && target_end != string::npos) { string target = value.substr(target_start + 1, target_end - 1 - target_start); tc = tcf.createTargetChooser(target, c); } manaCost->addExtraCost(NEW CounterCost(counter, tc)); break; } else if(value == "c") { manaCost->add(Constants::MTG_COLOR_WASTE, 1); break; } break; } default: //uncolored cost and hybrid costs and special cost { if(value == "unattach") { manaCost->addExtraCost(NEW UnattachCost(c)); break; } int intvalue = atoi(value.c_str()); int colors[2]; int values[2]; if (intvalue < 10 && value.size() > 1) { for (int i = 0; i < 2; i++) { char c = value[i]; if (c >= '0' && c <= '9') { colors[i] = Constants::MTG_COLOR_ARTIFACT; values[i] = c - '0'; } else { for (int j = 0; j < Constants::NB_Colors; j++) { if (c == Constants::MTGColorChars[j]) { colors[i] = j; values[i] = 1; } } } } if (values[0] > 0 || values[1] > 0) manaCost->addHybrid(colors[0], values[0], colors[1], values[1]); } else { manaCost->add(Constants::MTG_COLOR_ARTIFACT, intvalue); } break; } } } s = s.substr(end + 1); state = 0; } break; default: break; } } return manaCost; }
//WCFilterCMC bool WCFilterCMC::isMatch(MTGCard * c) { if (!c || !c->data) return false; ManaCost * mc = c->data->getManaCost(); return (mc->getConvertedCost() == number); }
int AIMomirPlayer::computeActions() { //Part of the strategy goes here. When should we put a land into play ? /* Another gift from Alex Majlaton on my first day playing Momir, and it has served me well ever since. It goes a little something like this: (a) if you are on the play, hit your Two through Four, skip your Five, and then hit all the way to Eight; (b) if you are on the draw and your opponent skips his One, you make Two through Eight; (c) if you are on the draw and your opponent hits a One, you match him drop-for-drop for the rest of the game. You skip your Five on the play because it is the weakest drop. There are plenty of serviceable guys there, but very few bombs compared to other drops the general rule is this: if you want to get to Eight, you have to skip two drops on the play and one drop on the draw. */ Player * p = observer->currentPlayer; if (!(observer->currentlyActing() == this)) return 0; if (chooseTarget()) return 1; int currentGamePhase = observer->getCurrentGamePhase(); if (observer->isInterrupting == this) { // interrupting selectAbility(); return 1; } else if (p == this && observer->mLayers->stackLayer()->count(0, NOT_RESOLVED) == 0) { //standard actions CardDescriptor cd; MTGCardInstance * card = NULL; switch (currentGamePhase) { case MTG_PHASE_FIRSTMAIN: { ManaCost * potentialMana = getPotentialMana(); int converted = potentialMana->getConvertedCost(); SAFE_DELETE(potentialMana); if (converted < 8 || game->hand->nb_cards > 1) { //Attempt to put land into play cd.init(); cd.setColor(Constants::MTG_COLOR_LAND); card = cd.match(game->hand); int canPutLandsIntoPlay = game->playRestrictions->canPutIntoZone(card, game->inPlay); if (card && (canPutLandsIntoPlay == PlayRestriction::CAN_PLAY)) { MTGAbility * putIntoPlay = observer->mLayers->actionLayer()->getAbility(MTGAbility::PUT_INTO_PLAY); AIAction * a = NEW AIAction(this, putIntoPlay, card); //TODO putinplay action clickstream.push(a); return 1; } } momir(); return 1; break; } case MTG_PHASE_SECONDMAIN: selectAbility(); return 1; break; default: return AIPlayerBaka::computeActions(); break; } } return AIPlayerBaka::computeActions(); }
//if it's not part of a combo or there is more to gather, then return false bool AIHints::canWeCombo(GameObserver* observer,MTGCardInstance * card,AIPlayerBaka * Ai) { TargetChooserFactory tfc(observer); TargetChooser * hintTc = NULL; bool gotCombo = false; int comboPartsHold = 0; int comboPartsUntil = 0; int comboPartsRestriction = 0; for(unsigned int i = 0; i < hints.size();i++) { comboPartsHold = 0; comboPartsUntil = 0; comboPartsRestriction = 0; if(gotCombo) return gotCombo;//because more then one might be possible at any time. if (hints[i]->hold.size()) { for(unsigned int hPart = 0; hPart < hints[i]->hold.size(); hPart++) { hintTc = tfc.createTargetChooser(hints[i]->hold[hPart],card); int TcCheck = hintTc->countValidTargets(); if(hintTc && TcCheck >= hintTc->maxtargets) { comboPartsHold +=1; } SAFE_DELETE(hintTc); } } if (hints[i]->until.size()) { for(unsigned int hPart = 0; hPart < hints[i]->until.size(); hPart++) { hintTc = tfc.createTargetChooser(hints[i]->until[hPart],card); int TcCheck = hintTc->countValidTargets(); if(hintTc && TcCheck >= hintTc->maxtargets) { comboPartsUntil +=1; } SAFE_DELETE(hintTc); } } if (hints[i]->restrict.size()) { for(unsigned int hPart = 0; hPart < hints[i]->restrict.size(); hPart++) { AbilityFactory af(observer); int checkCond = af.parseCastRestrictions(card,card->controller(),hints[i]->restrict[hPart]); if(checkCond >= 1) { comboPartsRestriction +=1; } } } if( comboPartsUntil >= int(hints[i]->until.size()) && comboPartsHold >= int(hints[i]->hold.size()) && comboPartsRestriction >= int(hints[i]->restrict.size()) && hints[i]->combos.size() ) { ManaCost * needed = ManaCost::parseManaCost(hints[i]->manaNeeded, NULL, card); if(Ai->canPayManaCost(card,needed).size()||!needed->getConvertedCost()) { gotCombo = true; Ai->comboHint = hints[i];//set the combo we are doing. } SAFE_DELETE(needed); } } return gotCombo; }