//+----------------------------------------------------------------------------+ //| Description: Magic Set Editor - Program to make Magic (tm) cards | //| Copyright: (C) 2001 - 2008 Twan van Laarhoven and "coppro" | //| License: GNU General Public License 2 or later (see file COPYING) | //+----------------------------------------------------------------------------+ // ----------------------------------------------------------------------------- : Includes #include #include #include #include #include #if !USE_NEW_PACK_SYSTEM // =================================================================================================== OLD DECLARE_TYPEOF_COLLECTION(PackItemRefP); DECLARE_TYPEOF_COLLECTION(PackItemP); DECLARE_TYPEOF_COLLECTION(CardP); // ----------------------------------------------------------------------------- : PackType PackType::PackType() : enabled(true) {} IMPLEMENT_REFLECTION(PackType) { REFLECT(name); REFLECT(enabled); REFLECT(items); } bool PackType::update(Context& ctx) { bool change = enabled.update(ctx); FOR_EACH(item, items) { change |= item->update(ctx); } return change; } void PackType::generate(PackItemCache& packs, boost::mt19937& gen, vector& out) const { FOR_EACH_CONST(item, items) { item->generate(packs,gen,out); } } // ----------------------------------------------------------------------------- : PackRefType IMPLEMENT_REFLECTION_ENUM(PackRefType) { VALUE_N("replace", PACK_REF_REPLACE); VALUE_N("no replace", PACK_REF_NO_REPLACE); VALUE_N("cyclic", PACK_REF_CYCLIC); } // ----------------------------------------------------------------------------- : PackItemRef PackItemRef::PackItemRef() : amount(1) , type(PACK_REF_NO_REPLACE) {} IMPLEMENT_REFLECTION(PackItemRef) { REFLECT(name); REFLECT(amount); REFLECT(type); } bool PackItemRef::update(Context& ctx) { return amount.update(ctx); } /// Random generator with random numbers in a range template struct RandomRange { RandomRange(Gen& gen) : gen(gen) {} unsigned operator () (unsigned max) { return gen() % max; } Gen& gen; }; void PackItemRef::generate(PackItemCache& packs, boost::mt19937& gen, vector& out) const { vector& cards = packs.cardsFor(name); // generate 'amount' cards and add them to out if (cards.empty()) return; if (type == PACK_REF_REPLACE) { // amount random numbers for (int i = 0 ; i < amount ; ++i) { size_t index = gen() % cards.size(); out.push_back(cards[index]); } } else if (type == PACK_REF_NO_REPLACE) { // random shuffle // to prevent us from being too predictable for small sets, periodically reshuffle RandomRange gen_range(gen); size_t max_per_batch = (cards.size() + 1) / 2; int rem = amount; while (rem > 0) { random_shuffle(cards.begin(), cards.end(), gen_range); out.insert(out.end(), cards.begin(), cards.begin() + min((size_t)rem, max_per_batch)); rem -= (int)max_per_batch; } } else if (type == PACK_REF_CYCLIC) { // multiple copies size_t copies = amount / cards.size(); FOR_EACH_CONST(card, cards) { out.insert(out.end(),copies,card); } // TODO: what if amount is not a multiple of the number of cards? } } // ----------------------------------------------------------------------------- : PackItem IMPLEMENT_REFLECTION(PackItem) { REFLECT(name); REFLECT(filter); } void PackItem::generate(Set& set, vector& out) const { FOR_EACH(card, set.cards) { Context& ctx = set.getContext(card); bool keep = *filter.invoke(ctx); if (keep) { out.push_back(card); } } } // ----------------------------------------------------------------------------- : PackItemCache PackItemCache::PackItemCache(Set& set) : set(set) {} vector& PackItemCache::cardsFor(const String& name) { // lookup name map::iterator it = item_cards.find(name); if (it != item_cards.end()) { return *it->second; } else { // not used before, generate list and cache FOR_EACH(item, set.game->pack_items) { if (item->name == name) { Cards cards(new vector); item->generate(set,*cards); item_cards.insert(make_pair(name,cards)); return *cards; } } // still not found throw Error(_ERROR_1_("pack item not found",name)); } } #else // =================================================================================================== NEW DECLARE_TYPEOF_COLLECTION(PackTypeP); DECLARE_TYPEOF_COLLECTION(PackItemP); DECLARE_TYPEOF_COLLECTION(CardP); DECLARE_TYPEOF_CONST(map); // ----------------------------------------------------------------------------- : PackType IMPLEMENT_REFLECTION_ENUM(PackSelectType) { VALUE_N("auto", SELECT_AUTO); VALUE_N("all", SELECT_ALL); VALUE_N("replace", SELECT_REPLACE); VALUE_N("no replace", SELECT_NO_REPLACE); VALUE_N("cyclic", SELECT_CYCLIC); VALUE_N("proportional",SELECT_PROPORTIONAL); VALUE_N("nonempty", SELECT_NONEMPTY); VALUE_N("first", SELECT_FIRST); } IMPLEMENT_REFLECTION(PackType) { REFLECT(name); REFLECT(enabled); REFLECT(selectable); REFLECT(summary); REFLECT(select); REFLECT(filter); REFLECT(items); REFLECT_IF_READING { if (select == SELECT_AUTO) { if (filter) select = SELECT_NO_REPLACE; else if (!items.empty()) select = SELECT_ALL; } if (indeterminate(summary)) { if (filter) summary = true; else if (!items.empty()) summary = false; } if (indeterminate(selectable)) { if (filter) selectable = false; else if (!items.empty()) selectable = true; } } } IMPLEMENT_REFLECTION(PackItem) { if (!tag.isComplex()) { REFLECT_NAMELESS(name); } else { REFLECT(name); REFLECT(amount); REFLECT(probability); } } PackType::PackType() : enabled(true) , selectable(indeterminate) , summary(indeterminate) , select(SELECT_AUTO) {} PackItem::PackItem() : amount(1) , probability(1) {} PackItem::PackItem(const String& name, int amount) : name(name) , amount(amount) , probability(1) {} bool PackType::update(Context& ctx) { bool change = enabled.update(ctx); FOR_EACH(item, items) { change |= item->update(ctx); } return change; } bool PackItem::update(Context& ctx) { return amount.update(ctx) | probability.update(ctx); } // ----------------------------------------------------------------------------- : PackInstance PackInstance::PackInstance(const PackType& pack_type, PackGenerator& parent) : pack_type(pack_type) , parent(parent) , requested_copies(0) , card_copies(0) , expected_copies(0) { // Filter cards if (pack_type.filter) { FOR_EACH(card, parent.set->cards) { Context& ctx = parent.set->getContext(card); bool keep = *pack_type.filter.invoke(ctx); if (keep) { cards.push_back(card); } } } // Count items if (pack_type.select == SELECT_FIRST) { // count = count of first nonempty thing if (!cards.empty()) { count = 1; } else { FOR_EACH_CONST(item, pack_type.items) { count += parent.get(item->name).count; if (count > 0) break; } } } else { count = cards.size(); FOR_EACH_CONST(item, pack_type.items) { count += parent.get(item->name).count; } } // Sum of probabilities total_probability = cards.size(); FOR_EACH_CONST(item, pack_type.items) { if (pack_type.select == SELECT_PROPORTIONAL) { total_probability += item->probability * parent.get(item->name).count; } else if (pack_type.select == SELECT_NONEMPTY) { if (parent.get(item->name).count > 0) { total_probability += item->probability; } } else { total_probability += item->probability; } } // Depth depth = 0; FOR_EACH_CONST(item, pack_type.items) { depth = max(depth, 1 + parent.get(item->name).depth); } } void PackInstance::expect_copy(double copies) { this->expected_copies += copies; // propagate FOR_EACH_CONST(item, pack_type.items) { PackInstance& i = parent.get(item->name); if (pack_type.select == SELECT_ALL) { i.expect_copy(copies * item->amount); } else if (pack_type.select == SELECT_PROPORTIONAL) { i.expect_copy(copies * item->amount * item->probability * i.count / total_probability); } else if (pack_type.select == SELECT_NONEMPTY) { if (i.count > 0) { i.expect_copy(copies * item->amount * item->probability / total_probability); } } else if (pack_type.select == SELECT_FIRST) { if (i.count > 0 && cards.empty()) { i.expect_copy(copies * item->amount); break; } } else { i.expect_copy(copies * item->amount * item->probability / total_probability); } } } void PackInstance::request_copy(size_t copies) { requested_copies += copies; } /// Random generator with random numbers in a range template struct RandomRange { RandomRange(Gen& gen) : gen(gen) {} unsigned operator () (unsigned max) { return gen() % max; } Gen& gen; }; void PackInstance::generate(vector* out) { card_copies = 0; if (requested_copies == 0) return; if (pack_type.select == SELECT_ALL) { // add all cards card_copies += requested_copies * cards.size(); if (out) { for (size_t i = 0 ; i < requested_copies ; ++i) { out->insert(out->end(), cards.begin(), cards.end()); } } // and all items FOR_EACH_CONST(item, pack_type.items) { PackInstance& i = parent.get(item->name); i.request_copy(requested_copies * item->amount); } } else if (pack_type.select == SELECT_REPLACE || pack_type.select == SELECT_PROPORTIONAL || pack_type.select == SELECT_NONEMPTY) { // multiple copies for (size_t i = 0 ; i < requested_copies ; ++i) { double r = parent.gen() * total_probability / parent.gen.max(); if (r < cards.size()) { // pick a card card_copies++; if (out) { int i = (int)r; out->push_back(cards[i]); } } else { // pick an item r -= cards.size(); FOR_EACH_CONST(item, pack_type.items) { PackInstance& i = parent.get(item->name); if (pack_type.select == SELECT_REPLACE) { r -= item->probability; } else if (pack_type.select == SELECT_PROPORTIONAL) { r -= item->probability * i.count; } else { // SELECT_NONEMPTY if (i.count > 0) r -= item->probability; } // have we reached the item we were looking for? if (r < 0) { i.request_copy(item->amount); break; } } } } } else if (pack_type.select == SELECT_NO_REPLACE) { card_copies += requested_copies; // NOTE: there is no way to pick items without replacement if (out && !cards.empty()) { // to prevent us from being too predictable for small sets, periodically reshuffle RandomRange gen_range(parent.gen); int max_per_batch = ((int)cards.size() + 1) / 2; int rem = (int)requested_copies; while (rem > 0) { random_shuffle(cards.begin(), cards.end(), gen_range); out->insert(out->end(), cards.begin(), cards.begin() + min(rem, max_per_batch)); rem -= max_per_batch; } } } else if (pack_type.select == SELECT_CYCLIC) { size_t total = cards.size() + pack_type.items.size(); if (total == 0) return; // prevent div by 0 size_t div = requested_copies / total; size_t rem = requested_copies % total; for (size_t i = 0 ; i < total ; ++i) { // how many copies of this card/item do we need? size_t copies = div + (i < rem ? 1 : 0); if (i < cards.size()) { card_copies += copies; if (out) out->insert(out->end(), copies, cards[i]); } else { const PackItemP& item = pack_type.items[i]; parent.get(item->name).request_copy(copies * item->amount); } } } else if (pack_type.select == SELECT_FIRST) { if (!cards.empty()) { // there is a card, pick it card_copies += requested_copies; if (out) out->push_back(cards.front()); } else { // pick first nonempty item FOR_EACH_CONST(item, pack_type.items) { PackInstance& i = parent.get(item->name); if (i.count > 0) { i.request_copy(requested_copies * item->amount); break; } } } } requested_copies = 0; } // ----------------------------------------------------------------------------- : PackGenerator void PackGenerator::reset(const SetP& set, int seed) { this->set = set; gen.seed((unsigned)seed); max_depth = 0; instances.clear(); } PackInstance& PackGenerator::get(const String& name) { PackInstanceP& instance = instances[name]; if (instance) { return *instance; } else { FOR_EACH_CONST(type, set->game->pack_types) { if (type->name == name) { instance = PackInstanceP(new PackInstance(*type,*this)); max_depth = max(max_depth, instance->get_depth()); return *instance; } } throw Error(_ERROR_1_("pack type not found",name)); } } PackInstance& PackGenerator::get(const PackTypeP& type) { return get(type->name); } void PackGenerator::generate(vector& out) { if (!set) return; // We generate from depth max_depth to 0 // instances can refer to other instances of lower depth, and generate // can change the number of copies of those lower depth instances for (int depth = max_depth ; depth >= 0 ; --depth) { // in game file order FOR_EACH_CONST(type, set->game->pack_types) { PackInstance& i = get(type); if (i.get_depth() == depth) { i.generate(&out); } } } } void PackGenerator::update_card_counts() { if (!set) return; // update card_counts by using generate() for (int depth = max_depth ; depth >= 0 ; --depth) { FOR_EACH_CONST(i,instances) { if (i.second->get_depth() == depth) { i.second->generate(nullptr); } } } } #endif