Commit 1cf4d1c8 authored by twanvl's avatar twanvl

the new pack system now actually works (yay!)

parent 41b7ae72
...@@ -153,26 +153,27 @@ vector<CardP>& PackItemCache::cardsFor(const String& name) { ...@@ -153,26 +153,27 @@ vector<CardP>& PackItemCache::cardsFor(const String& name) {
} }
#else #else
// =================================================================================================== NEW // =================================================================================================== NEW
DECLARE_TYPEOF_COLLECTION(PackTypeP); DECLARE_TYPEOF_COLLECTION(PackTypeP);
DECLARE_TYPEOF_COLLECTION(PackItemP); DECLARE_TYPEOF_COLLECTION(PackItemP);
DECLARE_TYPEOF_COLLECTION(CardP); DECLARE_TYPEOF_COLLECTION(CardP);
DECLARE_TYPEOF_CONST(map<String COMMA PackInstanceP>);
// ----------------------------------------------------------------------------- : PackType // ----------------------------------------------------------------------------- : PackType
PackType::PackType()
: enabled(true)
, selectable(true)
, summary(true)
, select(SELECT_ALL)
{}
IMPLEMENT_REFLECTION_ENUM(OneMany) { IMPLEMENT_REFLECTION_ENUM(PackSelectType) {
VALUE_N("auto", SELECT_AUTO);
VALUE_N("all", SELECT_ALL); VALUE_N("all", SELECT_ALL);
VALUE_N("at most one", SELECT_ONE_OR_EMPTY); VALUE_N("replace", SELECT_REPLACE);
VALUE_N("one", SELECT_ONE); VALUE_N("no replace", SELECT_NO_REPLACE);
VALUE_N("cyclic", SELECT_CYCLIC);
VALUE_N("one", SELECT_PROPORTIONAL);
VALUE_N("nonempty", SELECT_NONEMPTY);
VALUE_N("first", SELECT_FIRST); VALUE_N("first", SELECT_FIRST);
} }
...@@ -182,133 +183,133 @@ IMPLEMENT_REFLECTION(PackType) { ...@@ -182,133 +183,133 @@ IMPLEMENT_REFLECTION(PackType) {
REFLECT(selectable); REFLECT(selectable);
REFLECT(summary); REFLECT(summary);
REFLECT(select); REFLECT(select);
REFLECT(cards); REFLECT(filter);
REFLECT(items); REFLECT(items);
REFLECT_IF_READING {
if (select == SELECT_AUTO) {
if (filter) select = SELECT_NO_REPLACE;
else if (!items.empty()) select = SELECT_ALL;
}
}
} }
bool PackType::update(Context& ctx) { IMPLEMENT_REFLECTION(PackItem) {
bool change = enabled.update(ctx); REFLECT(name);
FOR_EACH(item, items) { REFLECT(amount);
change |= item->update(ctx); REFLECT(probability);
}
return change;
} }
// ----------------------------------------------------------------------------- : PackItem
PackType::PackType()
: enabled(true)
, selectable(true)
, summary(true)
, select(SELECT_AUTO)
{}
PackItem::PackItem() PackItem::PackItem()
: amount(1) : amount(1)
, type(PACK_REF_INHERIT)
{} {}
IMPLEMENT_REFLECTION_ENUM(PackSelectType) {
VALUE_N("inherit", PACK_REF_INHERIT);
VALUE_N("replace", PACK_REF_REPLACE);
VALUE_N("no replace", PACK_REF_NO_REPLACE);
VALUE_N("cyclic", PACK_REF_CYCLIC);
}
IMPLEMENT_REFLECTION(PackItem) { bool PackType::update(Context& ctx) {
REFLECT(pack); bool change = enabled.update(ctx);
REFLECT(amount); FOR_EACH(item, items) {
REFLECT(type); change |= item->update(ctx);
}
return change;
} }
bool PackItem::update(Context& ctx) { bool PackItem::update(Context& ctx) {
return amount.update(ctx); return amount.update(ctx)
| probability.update(ctx);
} }
// ----------------------------------------------------------------------------- : PackItemCache
ScriptValueP PackItemCache::cardsFor(const ScriptValueP& generate) { // ----------------------------------------------------------------------------- : PackInstance
// lookup name
ScriptValueP& value = item_cards[generate];
if (!value) {
value = generate->eval(set.getContext());
}
return value;
}
const PackType& PackItemCache::pack(const String& name) { PackInstance::PackInstance(const PackType& pack_type, PackGenerator& parent)
// not used before, generate list and cache : pack_type(pack_type)
FOR_EACH(pack, set.game->pack_types) { , parent(parent)
if (pack->name == name) { , requested_copies(0)
return *pack; , 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);
} }
} }
// not found
throw Error(_ERROR_1_("pack type not found", name));
}
// ----------------------------------------------------------------------------- : Counting expected cards
double PackItemCounter::probabilityNonEmpty(const PackType& pack) {
// TODO: cache?
if (pack.cards) {
return cardsFor(pack.cards.getScriptP())->itemCount();
} else if (pack.select == SELECT_ONE_OR_EMPTY) {
// weighted avarage
double p = 0.0;
double total_weight = 0.0;
FOR_EACH_CONST(i,pack.items) {
p += i->weight * probabilityNonEmpty(*i);
total_weight += i->weight;
}
return p / total_weight;
} else { // SELECT_ONE, SELECT_FIRST, SELECT_ALL
// disjunction
double p = 0.0;
FOR_EACH_CONST(i,pack.items) {
// either already non-empty, or all previous items were empty so pick this one
p += (1-p) * probabilityNonEmpty(*i);
if (p >= 1 - 1e-6) return 1.0;
}
return p;
} }
} // Count items
double PackItemCounter::probabilityNonEmpty(const PackItem& item) { if (pack_type.select == SELECT_FIRST) {
return item.amount <= 0 ? 0 : probabilityNonEmpty(pack(item.pack)); // 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 PackItemCounter::addCountRecursive(const PackType& pack, double copies) { void PackInstance::expect_copy(double copies) {
// add this->expected_copies += copies;
counts[&pack] += copies * probabilityNonEmpty(pack); // propagate
// recurse FOR_EACH_CONST(item, pack_type.items) {
if (pack.cards) { PackInstance& i = parent.get(item->name);
// done if (pack_type.select == SELECT_ALL) {
} else if (pack.select == SELECT_FIRST) { i.expect_copy(copies * item->amount);
double p = 1; } else if (pack_type.select == SELECT_PROPORTIONAL) {
FOR_EACH_CONST(i, pack.items) { i.expect_copy(copies * item->amount * item->probability * i.count / total_probability);
addCountRecursive(*i, p * copies); } else if (pack_type.select == SELECT_NONEMPTY) {
p *= 1 - probabilityNonEmpty(*i); if (i.count >= 0) {
if (p < 1e-6) return; i.expect_copy(copies * item->amount * item->probability / total_probability);
} }
} else if (pack.select == SELECT_ONE_OR_EMPTY || pack.select == SELECT_ONE) { } else if (pack_type.select == SELECT_FIRST) {
double total_weight = 0.0; if (i.count >= 0 && cards.empty()) {
FOR_EACH_CONST(i, pack.items) { i.expect_copy(copies * item->amount);
total_weight += i->weight * (pack.select == SELECT_ONE ? probabilityNonEmpty(*i) : 1.0); break;
}
FOR_EACH_CONST(i, pack.items) {
addCountRecursive(*i, copies * i->weight / total_weight);
}
} else if (pack.select == SELECT_ALL) {
FOR_EACH_CONST(i, pack.items) {
addCountRecursive(*i, copies);
} }
} else { } else {
throw InternalError(_("unknown OneMany value")); i.expect_copy(copies * item->amount * item->probability / total_probability);
}
} }
} }
void PackItemCounter::addCountRecursive(const PackItem& item, double copies) { void PackInstance::request_copy(size_t copies) {
addCountRecursive(pack(item.pack), item.amount * copies); requested_copies += copies;
} }
// ----------------------------------------------------------------------------- : Generating
DECLARE_TYPEOF(PackItemGenerator::OfTypeCount);
/// Random generator with random numbers in a range /// Random generator with random numbers in a range
template <typename Gen> template <typename Gen>
struct RandomRange { struct RandomRange {
...@@ -317,256 +318,161 @@ struct RandomRange { ...@@ -317,256 +318,161 @@ struct RandomRange {
Gen& gen; Gen& gen;
}; };
bool PackItemGenerator::generateCount(const PackType& pack, int copies, PackSelectType type, OfTypeCount& out) { void PackInstance::generate(vector<CardP>* out) {
if (copies <= 0) return false; card_copies = 0;
bool non_empty = false; if (pack_type.select == SELECT_ALL) {
if (pack.cards) { // add all cards
ScriptValueP the_cards = cardsFor(pack.cards.getScriptP()); card_copies += requested_copies * cards.size();
non_empty = the_cards->itemCount() > 0; if (out) {
if (non_empty) { for (size_t i = 0 ; i < requested_copies ; ++i) {
out[make_pair(the_cards,type)] += copies; out->insert(out->end(), cards.begin(), cards.end());
} }
} else if (pack.select == SELECT_ALL) {
// just generate all
FOR_EACH_CONST(i, pack.items) {
non_empty |= generateCount(*i, 1, type, out);
} }
} else { // and all items
// generate each copy separately FOR_EACH_CONST(item, pack_type.items) {
for (int j = 0 ; j < copies ; ++j) { PackInstance& i = parent.get(item->name);
non_empty |= generateSingleCount(pack, type, out); i.request_copy(requested_copies * item->amount);
} }
}
return non_empty;
}
bool PackItemGenerator::generateSingleCount(const PackType& pack, PackSelectType type, OfTypeCount& out) { } else if (pack_type.select == SELECT_REPLACE
if (pack.select == SELECT_ONE_OR_EMPTY) { || pack_type.select == SELECT_PROPORTIONAL
// pick a random item by weight || pack_type.select == SELECT_NONEMPTY) {
double total_weight = 0.0; // multiple copies
FOR_EACH_CONST(i, pack.items) { for (size_t i = 0 ; i < requested_copies ; ++i) {
total_weight += i->weight; double r = rand() * total_probability;
} if (r < cards.size()) {
double choice = gen() * total_weight / gen.max(); // pick a card
FOR_EACH_CONST(i, pack.items) { card_copies++;
if ((choice -= i->weight) <= 0) { if (out) {
// pick this one int i = (int)r;
return generateCount(*i, 1, type, out); out->push_back(cards[i]);
} }
}
} else if (pack.select == SELECT_ONE) {
// pick a random item by weight that is not empty
UInt possible = 0; // bitmask
double total_weight = 0.0;
for (size_t i = 0 ; i < pack.items.size() ; ++i) {
total_weight += pack.items[i]->weight;
possible |= 1 << i;
}
while (possible) {
// try to make a choice we have not made before
int choice = gen() * total_weight / gen.max();
for (size_t i = 0 ; i < pack.items.size() ; ++i) {
const PackItem& item = *pack.items[i];
if (!(possible & (1<<i))) continue; // already tried this item?
if ((choice -= item.weight) <= 0) {
bool non_empty = generateCount(item, 1, type, out);
if (non_empty) {
// found a non-empty choice, done
return true;
} else { } else {
// try again, exclude this item // pick an item
possible &= ~(1 << i); r -= cards.size();
total_weight -= item.weight; 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(requested_copies * item->amount);
break; break;
} }
} }
} }
} }
} else if (pack.select == SELECT_FIRST) {
// pick the first one that is not empty } else if (pack_type.select == SELECT_NO_REPLACE) {
FOR_EACH_CONST(i, pack.items) { card_copies += requested_copies;
bool non_empty = generateCount(*i, 1, type, out); // NOTE: there is no way to pick items without replacement
if (non_empty) return true; if (out) {
// to prevent us from being too predictable for small sets, periodically reshuffle
RandomRange<boost::mt19937> 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 {
throw InternalError(_("unknown OneMany value"));
} }
return false;
}
bool PackItemGenerator::generateCount(const PackItem& item, int copies, PackSelectType type, OfTypeCount& out) {
return generateCount(pack(item.pack), copies * item.amount, item.type == PACK_REF_INHERIT ? type : item.type, out);
}
void PackItemGenerator::generate(const PackType& pack) { } else if (pack_type.select == SELECT_CYCLIC) {
// first determine how many cards of each basic type we need size_t total = cards.size() + pack_type.items.size();
OfTypeCount counts; size_t div = requested_copies / total;
generateCount(pack, 1, PACK_REF_NO_REPLACE, counts); size_t rem = requested_copies % total;
// now select these cards for (size_t i = 0 ; i < total ; ++i) {
FOR_EACH(c, counts) { // how many copies of this card/item do we need?
pickCards(c.first.first, c.first.second, c.second); 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);
} }
}
void PackItemGenerator::pickCards(const ScriptValueP& cards, PackSelectType type, int amount) {
// generate 'amount' cards and add them to out
int cards_size = cards->itemCount();
if (cards_size <= 0) return;
RandomRange<boost::mt19937> gen_range(gen);
if (type == PACK_REF_REPLACE) {
// amount random numbers
for (int i = 0 ; i < amount ; ++i) {
int index = gen_range(cards_size);
out.push_back(from_script<CardP>(cards->getIndex(index)));
} }
} else if (type == PACK_REF_NO_REPLACE) {
// random shuffle } else if (pack_type.select == SELECT_FIRST) {
// to prevent us from being too predictable for small sets, periodically reshuffle if (!cards.empty()) {
int max_per_batch = (cards_size + 1) / 2; // there is a card, pick it
while (amount > 0) { card_copies += requested_copies;
int to_add = min(amount, max_per_batch); if (out) out->push_back(cards.front());
size_t old_out_size = out.size(); } else {
// add all to output temporarily // pick first nonempty item
ScriptValueP it = cards->makeIterator(cards); FOR_EACH_CONST(item, pack_type.items) {
while (ScriptValueP card = it->next()) { PackInstance& i = parent.get(item->name);
out.push_back(from_script<CardP>(card)); if (i.count >= 0) {
} i.request_copy(requested_copies * item->amount);
// shuffle and keep only the first to_add break;
random_shuffle(out.begin() + old_out_size, out.end(), gen_range);
out.resize(old_out_size + to_add);
amount -= to_add;
} }
} else if (type == PACK_REF_CYCLIC) {
// multiple copies
int copies = amount / cards_size;
ScriptValueP it = cards->makeIterator(cards);
while (ScriptValueP card = it->next()) {
out.insert(out.end(), copies, from_script<CardP>(card));
} }
amount -= copies * cards_size;
// if amount is not a multiple of the number of cards, pick the rest at random
for (int i = 0 ; i < amount ; ++i) {
int index = gen_range(cards_size);
out.push_back(from_script<CardP>(cards->getIndex(index)));
} }
} }
requested_copies = 0;
} }
// ----------------------------------------------------------------------------- : PackGenerator
/*//% void PackGenerator::reset(const SetP& set, int seed) {
// ----------------------------------------------------------------------------- : PackItem this->set = set;
gen.seed((unsigned)seed);
void PackItemCounter::count(const String& name, double amount) { max_depth = 0;
map<PackItemRef*,double>::iterator it = sizes.find(name); instances.clear();
if (it != sizes.end()) return it->second;
size *= amount * probability;
return sizes[&item] = size;
} }
void PackItemCounter::count(const PackType& pack, int copies) {
double size = 0; PackInstance& PackGenerator::get(const String& name) {
if (pack.select = ONE) { PackInstanceP& instance = instances[name];
double total_size; if (instance) {
FOR_EACH(item, pack.items) { return *instance;
double item_size = size(item);
if (item->type == OTHER_IF_EMPTY) {
// is it empty?
total_size += item_size > 0 ? 1 : 0;
max_size += 1;
} else if (item->type == WEIGHTED) {
total_size += item_size;
max_size += item_size;
} else {
total_size += 1;
max_size += 1;
}
}
size += total_size / max_size;
// count
FOR_EACH(item, pack.items) {
double item_size = size(item);
if (item_size > 0 && !item->cards) {
if (item->type == OTHER_IF_EMPTY) {
// is it empty?
total_size += item_size > 0 ? 1 : 0;
max_size += 1;
} else if (item->type == WEIGHTED) {
total_size += item_size;
max_size += item_size;
} else { } else {
total_size += 1; FOR_EACH_CONST(type, set->game->pack_types) {
max_size += 1; 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));
} }
} else { // MANY
}
amounts[pack.name] += amount;
} }
double PackItemCounter::size(const String& name) { PackInstance& PackGenerator::get(const PackTypeP& type) {
map<PackItemRef*,double>::iterator it = sizes.find(name); return get(type->name);
if (it != sizes.end()) return it->second;
double the_size = 0;
FOR_EACH() {
the_size += size(item);
}
return sizes[&item] = the_size;
} }
double PackItemCounter::size(const PackItemRef& item) {
String the_name; void PackGenerator::generate(vector<CardP>& out) {
double the_size; if (!set) return;
if (cards) { // We generate from depth max_depth to 0
the_size = cards.invokeOn(ctx)->itemCount() > 0 ? 1 : 0; // instances can refer to other instances of lower depth, and generate
} else { // can change the number of copies of those lower depth instances
the_size = size(name); for (int depth = max_depth ; depth >= 0 ; --depth) {
the_name = name; // in game file order
FOR_EACH_CONST(type, set->game->pack_types) {
PackInstance& i = get(type);
if (i.get_depth() == depth) {
i.generate(&out);
} }
if (the_size == 0 && !if_empty.empty())
the_name = if_empty;
the_size = size(if_empty);
} }
if (!the_name.empty()) {
counts[the_name] += amount * probability;
} }
return the_size * amount * probability;
} }
void PackGenerator::update_card_counts() {
void PackItemCounter::count(const PackType& pack, int copies, type) { if (!set) return;
if (pack.cards) { // update card_counts by using generate()
// for (int depth = max_depth ; depth >= 0 ; --depth) {
cards =.. FOR_EACH_CONST(i,instances) {
if (!cards.empty()) { if (i.second->get_depth() == depth) {
i.second->generate(nullptr);
return true;
} else {
return false;
} }
} else {
}
}
// ----------------------------------------------------------------------------- : PackItem
IMPLEMENT_REFLECTION(PackItem) {
REFLECT(name);
REFLECT(filter);
}
void PackItem::generate(Set& set, vector<CardP>& 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)
{}*/
#endif #endif
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
#include <script/scriptable.hpp> #include <script/scriptable.hpp>
#include <boost/random/mersenne_twister.hpp> #include <boost/random/mersenne_twister.hpp>
#define USE_NEW_PACK_SYSTEM 0 #define USE_NEW_PACK_SYSTEM 1
#if !USE_NEW_PACK_SYSTEM #if !USE_NEW_PACK_SYSTEM
// =================================================================================================== OLD // =================================================================================================== OLD
...@@ -103,20 +103,29 @@ class PackItemCache { ...@@ -103,20 +103,29 @@ class PackItemCache {
}; };
#else #else
// =================================================================================================== NEW // =================================================================================================== NEW
DECLARE_POINTER_TYPE(PackType);
DECLARE_POINTER_TYPE(PackItem); DECLARE_POINTER_TYPE(PackItem);
DECLARE_POINTER_TYPE(PackInstance);
DECLARE_POINTER_TYPE(Card); DECLARE_POINTER_TYPE(Card);
class Set; DECLARE_POINTER_TYPE(Set);
class PackGenerator;
// ----------------------------------------------------------------------------- : PackType // ----------------------------------------------------------------------------- : PackType
enum OneMany enum PackSelectType
{ SELECT_ONE_OR_EMPTY { SELECT_AUTO
, SELECT_ONE
, SELECT_FIRST
, SELECT_ALL , SELECT_ALL
, SELECT_REPLACE
, SELECT_NO_REPLACE
, SELECT_CYCLIC
, SELECT_PROPORTIONAL
, SELECT_NONEMPTY
, SELECT_FIRST
}; };
/// A card pack description for playtesting /// A card pack description for playtesting
...@@ -128,8 +137,7 @@ class PackType : public IntrusivePtrBase<PackType> { ...@@ -128,8 +137,7 @@ class PackType : public IntrusivePtrBase<PackType> {
Scriptable<bool> enabled; ///< Is this pack enabled? Scriptable<bool> enabled; ///< Is this pack enabled?
bool selectable; ///< Is this pack listed in the UI? bool selectable; ///< Is this pack listed in the UI?
bool summary; ///< Should the total be listed for this type? bool summary; ///< Should the total be listed for this type?
OneMany select; ///< Select one or many? PackSelectType select; ///< What cards/items to select
OptionalScript cards; ///< Script to select this type of cards (there are no items)
OptionalScript filter; ///< Filter to select this type of cards OptionalScript filter; ///< Filter to select this type of cards
vector<PackItemP> items; ///< Subpacks in this pack vector<PackItemP> items; ///< Subpacks in this pack
...@@ -140,24 +148,14 @@ class PackType : public IntrusivePtrBase<PackType> { ...@@ -140,24 +148,14 @@ class PackType : public IntrusivePtrBase<PackType> {
DECLARE_REFLECTION(); DECLARE_REFLECTION();
}; };
// ----------------------------------------------------------------------------- : PackItem
enum PackSelectType
{ PACK_REF_INHERIT
, PACK_REF_REPLACE
, PACK_REF_NO_REPLACE
, PACK_REF_CYCLIC
};
/// An item in a PackType /// An item in a PackType
class PackItem : public IntrusivePtrBase<PackItem> { class PackItem : public IntrusivePtrBase<PackItem> {
public: public:
PackItem(); PackItem();
String pack; ///< Name of the pack to select cards from String name; ///< Name of the pack to select cards from
Scriptable<int> amount; ///< Number of cards of this type Scriptable<int> amount; ///< Number of cards of this type
Scriptable<double> weight; ///< Relative probability of picking this item Scriptable<double> probability; ///< Relative probability of picking this item
PackSelectType type;
/// Update scripts, returns true if there is a change /// Update scripts, returns true if there is a change
bool update(Context& ctx); bool update(Context& ctx);
...@@ -166,86 +164,65 @@ class PackItem : public IntrusivePtrBase<PackItem> { ...@@ -166,86 +164,65 @@ class PackItem : public IntrusivePtrBase<PackItem> {
DECLARE_REFLECTION(); DECLARE_REFLECTION();
}; };
// ----------------------------------------------------------------------------- : Generating / counting
// --------------------------------------------------- : PackItemCache // ----------------------------------------------------------------------------- : Generating / counting
class PackItemCache { // A PackType that is instantiated for a particular Set,
// i.e. we now know the actual cards
class PackInstance : public IntrusivePtrBase<PackInstance> {
public: public:
PackItemCache(Set& set) : set(set) {} PackInstance(const PackType& pack_type, PackGenerator& parent);
/// Look up a pack type by name /// Expect to pick this many copies from this pack, updates expected_copies
const PackType& pack(const String& name); void expect_copy(double copies = 1);
/// Request some copies of this pack
void request_copy(size_t copies = 1);
protected: /// Generate cards if depth == at_depth
Set& set; /** Some cards are (optionally) added to out and card_copies
* And also the copies of referenced items might be incremented
*
* Resets the count of this instance to 0 */
void generate(vector<CardP>* out);
/// The cards for a given PackItem inline int get_depth() const { return depth; }
ScriptValueP cardsFor(const ScriptValueP& cards_script); inline bool has_cards() const { return !cards.empty(); }
inline size_t get_card_copies() const { return card_copies; }
inline double get_expected_copies() const { return expected_copies; }
private: private:
/// Lookup PackTypes by name const PackType& pack_type;
//%% PackGenerator& parent;
/// Cards for each PackType int depth; //< 0 = no items, otherwise 1+max depth of items refered to
map<ScriptValueP,ScriptValueP> item_cards; vector<CardP> cards; //< All cards that pass the filter
size_t count; //< Total number of non-empty cards/items
double total_probability; //< Sum of item and card probabilities
size_t requested_copies; //< The requested number of copies of this pack
size_t card_copies; //< The number of cards that were chosen to come from this pack
double expected_copies;
}; };
// --------------------------------------------------- : Counting expected cards class PackGenerator {
/// Class for determining the *expected* number of cards from each type
class PackItemCounter : PackItemCache {
public: public:
PackItemCounter(Set& set, map<const PackType*,double>& counts) /// Reset the generator, possibly switching the set or reseeding
: PackItemCache(set), counts(counts) void reset(const SetP& set, int seed);
{}
/// Add a number of copies of the PackType to the counts, recurse into child items /// Find the PackInstance for the PackType with the given name
void addCountRecursive(const PackType& pack, double copies); PackInstance& get(const String& name);
void addCountRecursive(const PackItem& item, double copies); PackInstance& get(const PackTypeP& type);
/// The probability that the given pack is non-empty /// Generate all cards, resets copies
double probabilityNonEmpty(const PackType& pack); void generate(vector<CardP>& out);
double probabilityNonEmpty(const PackItem& item); /// Update all card_copies counters, resets copies
void update_card_counts();
/// The counts will be stored here
map<const PackType*,double>& counts;
// only for PackInstance
SetP set; ///< The set
boost::mt19937 gen; ///< Random generator
private: private:
/// The probability that a pack type is empty (cache) /// Details for each PackType
//%map<const PackItem*,double> probability_empty; map<String,PackInstanceP> instances;
}; int max_depth;
// --------------------------------------------------- : PackItemCounter
/// Class for generating card packs
class PackItemGenerator : PackItemCache {
public:
PackItemGenerator(Set& set, vector<CardP>& cards, boost::mt19937& gen)
: PackItemCache(set), out(cards), gen(gen)
{}
/// Generate a pack, adding it to cards
void generate(const PackType& pack);
/// Number of cards of a type
typedef map<pair<ScriptValueP,PackSelectType>,int> OfTypeCount;
/// Determine what *types* of cards to pick (store in out)
/** Does NOT add cards yet.
* Returns true if non-empty.
*/
bool generateCount(const PackType& pack, int copies, PackSelectType type, OfTypeCount& out);
bool generateCount(const PackItem& item, int copies, PackSelectType type, OfTypeCount& out);
bool generateSingleCount(const PackType& pack, PackSelectType type, OfTypeCount& out);
/// Pick cards from a list
void pickCards(const ScriptValueP& cards, PackSelectType type, int amount);
/// The cards will be stored here
vector<CardP>& out;
/// Random generator
boost::mt19937& gen;
}; };
// ----------------------------------------------------------------------------- : EOF // ----------------------------------------------------------------------------- : EOF
......
...@@ -88,10 +88,14 @@ void RandomCardList::getItems(vector<VoidP>& out) const { ...@@ -88,10 +88,14 @@ void RandomCardList::getItems(vector<VoidP>& out) const {
class PackTotalsPanel : public wxPanel { class PackTotalsPanel : public wxPanel {
public: public:
#if USE_NEW_PACK_SYSTEM
PackTotalsPanel(Window* parent, int id, PackGenerator& generator) : wxPanel(parent,id), generator(generator) {}
#else
PackTotalsPanel(Window* parent, int id) : wxPanel(parent,id) {} PackTotalsPanel(Window* parent, int id) : wxPanel(parent,id) {}
#endif
void setGame(const GameP& game); void setGame(const GameP& game);
void clear();
#if !USE_NEW_PACK_SYSTEM #if !USE_NEW_PACK_SYSTEM
void clear();
void addPack(PackType& pack, int copies); void addPack(PackType& pack, int copies);
void addItemRef(PackItemRef& item, int copies); void addItemRef(PackItemRef& item, int copies);
#endif #endif
...@@ -101,12 +105,11 @@ class PackTotalsPanel : public wxPanel { ...@@ -101,12 +105,11 @@ class PackTotalsPanel : public wxPanel {
void onPaint(wxPaintEvent&); void onPaint(wxPaintEvent&);
void draw(DC& dc); void draw(DC& dc);
void drawItem(DC& dc, int& y, const String& name, double value); void drawItem(DC& dc, int& y, const String& name, double value);
#if USE_NEW_PACK_SYSTEM #if USE_NEW_PACK_SYSTEM
public: PackGenerator& generator;
map<const PackType*,double> amounts; #else
#else
map<String,int> amounts; map<String,int> amounts;
#endif #endif
}; };
void PackTotalsPanel::onPaint(wxPaintEvent&) { void PackTotalsPanel::onPaint(wxPaintEvent&) {
...@@ -126,10 +129,10 @@ void PackTotalsPanel::draw(DC& dc) { ...@@ -126,10 +129,10 @@ void PackTotalsPanel::draw(DC& dc) {
int total = 0; int total = 0;
#if USE_NEW_PACK_SYSTEM #if USE_NEW_PACK_SYSTEM
FOR_EACH(pack, game->pack_types) { FOR_EACH(pack, game->pack_types) {
if (pack->summary) { PackInstance& i = generator.get(pack);
int value = amounts[pack.get()]; if (pack->summary && i.has_cards()) {
drawItem(dc, y, tr(*game, pack->name, capitalize), value); drawItem(dc, y, tr(*game, pack->name, capitalize), i.get_card_copies());
total += value; total += (int)i.get_card_copies();
} }
} }
#else #else
...@@ -160,12 +163,14 @@ void PackTotalsPanel::drawItem(DC& dc, int& y, const String& name, double value ...@@ -160,12 +163,14 @@ void PackTotalsPanel::drawItem(DC& dc, int& y, const String& name, double value
void PackTotalsPanel::setGame(const GameP& game) { void PackTotalsPanel::setGame(const GameP& game) {
this->game = game; this->game = game;
#if !USE_NEW_PACK_SYSTEM
clear(); clear();
} #endif
void PackTotalsPanel::clear() {
amounts.clear();
} }
#if !USE_NEW_PACK_SYSTEM #if !USE_NEW_PACK_SYSTEM
void PackTotalsPanel::clear() {
amounts.clear();
}
void PackTotalsPanel::addPack(PackType& pack, int copies) { void PackTotalsPanel::addPack(PackType& pack, int copies) {
FOR_EACH(item,pack.items) { FOR_EACH(item,pack.items) {
addItemRef(*item, copies * item->amount); addItemRef(*item, copies * item->amount);
...@@ -196,7 +201,11 @@ void RandomPackPanel::initControls() { ...@@ -196,7 +201,11 @@ void RandomPackPanel::initControls() {
seed_random = new wxRadioButton(this, ID_SEED_RANDOM, _BUTTON_("random seed")); seed_random = new wxRadioButton(this, ID_SEED_RANDOM, _BUTTON_("random seed"));
seed_fixed = new wxRadioButton(this, ID_SEED_FIXED, _BUTTON_("fixed seed")); seed_fixed = new wxRadioButton(this, ID_SEED_FIXED, _BUTTON_("fixed seed"));
seed = new wxTextCtrl(this, wxID_ANY); seed = new wxTextCtrl(this, wxID_ANY);
#if USE_NEW_PACK_SYSTEM
totals = new PackTotalsPanel(this, wxID_ANY, generator);
#else
totals = new PackTotalsPanel(this, wxID_ANY); totals = new PackTotalsPanel(this, wxID_ANY);
#endif
static_cast<SetWindow*>(GetParent())->setControlStatusText(seed_random, _HELP_("random seed")); static_cast<SetWindow*>(GetParent())->setControlStatusText(seed_random, _HELP_("random seed"));
static_cast<SetWindow*>(GetParent())->setControlStatusText(seed_fixed, _HELP_("fixed seed")); static_cast<SetWindow*>(GetParent())->setControlStatusText(seed_fixed, _HELP_("fixed seed"));
static_cast<SetWindow*>(GetParent())->setControlStatusText(seed, _HELP_("seed")); static_cast<SetWindow*>(GetParent())->setControlStatusText(seed, _HELP_("seed"));
...@@ -259,6 +268,9 @@ void RandomPackPanel::onChangeSet() { ...@@ -259,6 +268,9 @@ void RandomPackPanel::onChangeSet() {
// add pack controls // add pack controls
FOR_EACH(pack, set->game->pack_types) { FOR_EACH(pack, set->game->pack_types) {
#if NEW_PACK_SYSTEM
if (pack->selectable) {
#endif
PackItem i; PackItem i;
i.pack = pack; i.pack = pack;
i.label = new wxStaticText(this, wxID_ANY, capitalize_sentence(pack->name)); i.label = new wxStaticText(this, wxID_ANY, capitalize_sentence(pack->name));
...@@ -266,6 +278,9 @@ void RandomPackPanel::onChangeSet() { ...@@ -266,6 +278,9 @@ void RandomPackPanel::onChangeSet() {
packsSizer->Add(i.label, 0, wxALIGN_CENTER_VERTICAL); packsSizer->Add(i.label, 0, wxALIGN_CENTER_VERTICAL);
packsSizer->Add(i.value, 0, wxEXPAND | wxALIGN_CENTER); packsSizer->Add(i.value, 0, wxEXPAND | wxALIGN_CENTER);
packs.push_back(i); packs.push_back(i);
#if NEW_PACK_SYSTEM
}
#endif
} }
Layout(); Layout();
...@@ -280,6 +295,9 @@ void RandomPackPanel::onChangeSet() { ...@@ -280,6 +295,9 @@ void RandomPackPanel::onChangeSet() {
i.value->SetValue(gs.pack_amounts[i.pack->name]); i.value->SetValue(gs.pack_amounts[i.pack->name]);
} }
#if USE_NEW_PACK_SYSTEM
generator.reset(set,0);
#endif
updateTotals(); updateTotals();
} }
...@@ -328,20 +346,22 @@ void RandomPackPanel::onCommand(int id) { ...@@ -328,20 +346,22 @@ void RandomPackPanel::onCommand(int id) {
// ----------------------------------------------------------------------------- : Generating // ----------------------------------------------------------------------------- : Generating
void RandomPackPanel::updateTotals() { void RandomPackPanel::updateTotals() {
#if USE_NEW_PACK_SYSTEM #if !USE_NEW_PACK_SYSTEM
PackItemCounter counter(*set, totals->amounts);
#endif
totals->clear(); totals->clear();
total_packs = 0; #endif
int total_packs = 0;
FOR_EACH(i,packs) { FOR_EACH(i,packs) {
int copies = i.value->GetValue(); int copies = i.value->GetValue();
total_packs += copies; total_packs += copies;
#if USE_NEW_PACK_SYSTEM #if USE_NEW_PACK_SYSTEM
counter.addCountRecursive(*i.pack, copies); generator.get(i.pack).request_copy(copies);
#else #else
totals->addPack(*i.pack, copies); totals->addPack(*i.pack, copies);
#endif #endif
} }
#if USE_NEW_PACK_SYSTEM
generator.update_card_counts();
#endif
// update UI // update UI
totals->Refresh(false); totals->Refresh(false);
generate_button->Enable(total_packs > 0); generate_button->Enable(total_packs > 0);
...@@ -373,10 +393,10 @@ void RandomPackPanel::setSeed(int seed) { ...@@ -373,10 +393,10 @@ void RandomPackPanel::setSeed(int seed) {
} }
void RandomPackPanel::generate() { void RandomPackPanel::generate() {
boost::mt19937 gen((unsigned)getSeed());
#if USE_NEW_PACK_SYSTEM #if USE_NEW_PACK_SYSTEM
PackItemGenerator generator(*set, card_list->cards, gen); generator.reset(set,getSeed());
#else #else
boost::mt19937 gen((unsigned)getSeed());
PackItemCache pack_cache(*set); PackItemCache pack_cache(*set);
#endif #endif
// add packs to card list // add packs to card list
...@@ -385,7 +405,8 @@ void RandomPackPanel::generate() { ...@@ -385,7 +405,8 @@ void RandomPackPanel::generate() {
int copies = item.value->GetValue(); int copies = item.value->GetValue();
for (int i = 0 ; i < copies ; ++i) { for (int i = 0 ; i < copies ; ++i) {
#if USE_NEW_PACK_SYSTEM #if USE_NEW_PACK_SYSTEM
generator.generate(*item.pack); generator.get(item.pack).request_copy();
generator.generate(card_list->cards);
#else #else
card_list->add(pack_cache, gen, *item.pack); card_list->add(pack_cache, gen, *item.pack);
#endif #endif
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include <util/prec.hpp> #include <util/prec.hpp>
#include <gui/set/panel.hpp> #include <gui/set/panel.hpp>
#include <data/pack.hpp>
#include <wx/spinctrl.h> #include <wx/spinctrl.h>
class CardViewer; class CardViewer;
...@@ -66,7 +67,9 @@ class RandomPackPanel : public SetWindowPanel { ...@@ -66,7 +67,9 @@ class RandomPackPanel : public SetWindowPanel {
}; };
vector<PackItem> packs; vector<PackItem> packs;
int total_packs; #if USE_NEW_PACK_SYSTEM
PackGenerator generator;
#endif
/// Actual intialization of the controls /// Actual intialization of the controls
void initControls(); void initControls();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment