Commit 6b09bde7 authored by twanvl's avatar twanvl

while it is still possible: renamed select:cyclic to select:equal.

Added "equal proportional" and "equal nonempty" for symmetry.
select:equal now takes weights into account.
parent 3fc08f0e
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
pack type: pack type:
name: basic land name: basic land
select: cyclic select: equal
filter: card.rarity == "basic land" and not is_token_card() # can be shifted filter: card.rarity == "basic land" and not is_token_card() # can be shifted
pack type: pack type:
name: common name: common
...@@ -114,23 +114,22 @@ pack type: ...@@ -114,23 +114,22 @@ pack type:
name: common sometimes shifted or special name: common sometimes shifted or special
selectable: false selectable: false
# TODO: Perhaps use some kind of proportional system here as well? # TODO: Perhaps use some kind of proportional system here as well?
select: cyclic select: equal
item: common item:
item: common name: common
item: shifted common or else common weight: 6
item: common item:
item: common name: shifted common or else common
item: shifted common or else common weight: 3
item: common item:
item: common name: special or else common
item: shifted common or else common weight: 1
item: special or else common
# of the uncommon slots, 1/3 will be shifted, 1/4 of that will be shifted rares instead # of the uncommon slots, 1/3 will be shifted, 1/4 of that will be shifted rares instead
pack type: pack type:
name: uncommon sometimes shifted name: uncommon sometimes shifted
selectable: false selectable: false
select: cyclic select: equal
item: uncommon item: uncommon
item: uncommon item: uncommon
item: shifted uncommon or rare or else uncommon item: shifted uncommon or rare or else uncommon
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include <data/set.hpp> #include <data/set.hpp>
#include <data/game.hpp> #include <data/game.hpp>
#include <data/card.hpp> #include <data/card.hpp>
#include <queue>
#if !USE_NEW_PACK_SYSTEM #if !USE_NEW_PACK_SYSTEM
// =================================================================================================== OLD // =================================================================================================== OLD
...@@ -167,14 +168,16 @@ DECLARE_TYPEOF_CONST(map<String COMMA PackInstanceP>); ...@@ -167,14 +168,16 @@ DECLARE_TYPEOF_CONST(map<String COMMA PackInstanceP>);
IMPLEMENT_REFLECTION_ENUM(PackSelectType) { IMPLEMENT_REFLECTION_ENUM(PackSelectType) {
VALUE_N("auto", SELECT_AUTO); VALUE_N("auto", SELECT_AUTO);
VALUE_N("all", SELECT_ALL); VALUE_N("all", SELECT_ALL);
VALUE_N("replace", SELECT_REPLACE); VALUE_N("no replace", SELECT_NO_REPLACE);
VALUE_N("no replace", SELECT_NO_REPLACE); VALUE_N("replace", SELECT_REPLACE);
VALUE_N("cyclic", SELECT_CYCLIC); VALUE_N("proportional", SELECT_PROPORTIONAL);
VALUE_N("proportional",SELECT_PROPORTIONAL); VALUE_N("nonempty", SELECT_NONEMPTY);
VALUE_N("nonempty", SELECT_NONEMPTY); VALUE_N("equal", SELECT_EQUAL);
VALUE_N("first", SELECT_FIRST); VALUE_N("equal proportional", SELECT_EQUAL_PROPORTIONAL);
VALUE_N("equal nonempty", SELECT_NONEMPTY);
VALUE_N("first", SELECT_FIRST);
} }
IMPLEMENT_REFLECTION(PackType) { IMPLEMENT_REFLECTION(PackType) {
...@@ -264,32 +267,24 @@ PackInstance::PackInstance(const PackType& pack_type, PackGenerator& parent) ...@@ -264,32 +267,24 @@ PackInstance::PackInstance(const PackType& pack_type, PackGenerator& parent)
} }
} }
} }
// Count items // Sum of weights
if (pack_type.select == SELECT_FIRST) { if (pack_type.select == SELECT_FIRST) {
// count = count of first nonempty thing total_weight = cards.empty() ? 0 : 1;
if (!cards.empty()) {
count = 1;
} else {
FOR_EACH_CONST(item, pack_type.items) {
count += parent.get(item->name).count;
if (count > 0) break;
}
}
} else { } else {
count = cards.size(); total_weight = cards.size();
FOR_EACH_CONST(item, pack_type.items) {
count += parent.get(item->name).count;
}
} }
// Sum of weights
total_weight = cards.size();
FOR_EACH_CONST(item, pack_type.items) { FOR_EACH_CONST(item, pack_type.items) {
if (pack_type.select == SELECT_PROPORTIONAL) { if (pack_type.select == SELECT_PROPORTIONAL || pack_type.select == SELECT_EQUAL_PROPORTIONAL) {
total_weight += item->weight * parent.get(item->name).count; total_weight += item->weight * parent.get(item->name).total_weight;
} else if (pack_type.select == SELECT_NONEMPTY) { } else if (pack_type.select == SELECT_NONEMPTY || pack_type.select == SELECT_EQUAL_NONEMPTY) {
if (parent.get(item->name).count > 0) { if (parent.get(item->name).total_weight > 0) {
total_weight += item->weight; total_weight += item->weight;
} }
} else if (pack_type.select == SELECT_FIRST) {
if (total_weight <= 0) {
total_weight = item->weight;
break;
}
} else { } else {
total_weight += item->weight; total_weight += item->weight;
} }
...@@ -308,14 +303,14 @@ void PackInstance::expect_copy(double copies) { ...@@ -308,14 +303,14 @@ void PackInstance::expect_copy(double copies) {
PackInstance& i = parent.get(item->name); PackInstance& i = parent.get(item->name);
if (pack_type.select == SELECT_ALL) { if (pack_type.select == SELECT_ALL) {
i.expect_copy(copies * item->amount); i.expect_copy(copies * item->amount);
} else if (pack_type.select == SELECT_PROPORTIONAL) { } else if (pack_type.select == SELECT_PROPORTIONAL || pack_type.select == SELECT_EQUAL_PROPORTIONAL) {
i.expect_copy(copies * item->amount * item->weight * i.count / total_weight); i.expect_copy(copies * item->amount * item->weight * i.total_weight / total_weight);
} else if (pack_type.select == SELECT_NONEMPTY) { } else if (pack_type.select == SELECT_NONEMPTY || pack_type.select == SELECT_EQUAL_NONEMPTY) {
if (i.count > 0) { if (i.total_weight > 0) {
i.expect_copy(copies * item->amount * item->weight / total_weight); i.expect_copy(copies * item->amount * item->weight / total_weight);
} }
} else if (pack_type.select == SELECT_FIRST) { } else if (pack_type.select == SELECT_FIRST) {
if (i.count > 0 && cards.empty()) { if (i.total_weight > 0 && cards.empty()) {
i.expect_copy(copies * item->amount); i.expect_copy(copies * item->amount);
break; break;
} }
...@@ -337,55 +332,57 @@ struct RandomRange { ...@@ -337,55 +332,57 @@ struct RandomRange {
Gen& gen; Gen& gen;
}; };
struct WeightedItem {
double weight;
int count;
int tiebreaker;
};
struct CompareWeightedItems{
inline bool operator () (WeightedItem* a, WeightedItem* b) {
// compare (a->count+1)/a->weight <> (b->count+1)/b->weight
// prefer the one where this is lower, return true if b is prefered
double delta = b->weight * (a->count + 1) - a->weight * (b->count + 1);
if (delta < 0) return false;
if (delta > 0) return true;
return b->tiebreaker < a->tiebreaker;
}
};
/// Distribute 'total' among the weighted items, higher weight items get chosen more often
void weighted_equal_divide(vector<WeightedItem>& items, int total) {
assert(!items.empty());
if (items.size() == 1) {
items.front().count = total;
} else {
priority_queue<WeightedItem*,vector<WeightedItem*>,CompareWeightedItems> pq;
for (size_t i = 0 ; i < items.size() ; ++i) {
pq.push(&items[i]);
}
while (total > 0) {
// repeatedly pick the item that minimizes, after incrementing count:
// max_wi wi->count/wi->weight
WeightedItem* wi = pq.top();pq.pop();
wi->count++;
total--;
pq.push(wi);
}
}
}
void PackInstance::generate(vector<CardP>* out) { void PackInstance::generate(vector<CardP>* out) {
card_copies = 0; card_copies = 0;
if (requested_copies == 0) return; if (requested_copies == 0) return;
if (pack_type.select == SELECT_ALL) { if (pack_type.select == SELECT_ALL) {
// add all cards // add all cards
card_copies += requested_copies * cards.size(); generate_all(out, requested_copies);
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 } else if (pack_type.select == SELECT_REPLACE
|| pack_type.select == SELECT_PROPORTIONAL || pack_type.select == SELECT_PROPORTIONAL
|| pack_type.select == SELECT_NONEMPTY) { || pack_type.select == SELECT_NONEMPTY) {
// multiple copies // multiple copies
for (size_t i = 0 ; i < requested_copies ; ++i) { for (size_t i = 0 ; i < requested_copies ; ++i) {
double r = parent.gen() * total_weight / parent.gen.max(); generate_one_random(out);
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->weight;
} else if (pack_type.select == SELECT_PROPORTIONAL) {
r -= item->weight * i.count;
} else { // SELECT_NONEMPTY
if (i.count > 0) r -= item->weight;
}
// 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) { } else if (pack_type.select == SELECT_NO_REPLACE) {
...@@ -406,20 +403,53 @@ void PackInstance::generate(vector<CardP>* out) { ...@@ -406,20 +403,53 @@ void PackInstance::generate(vector<CardP>* out) {
} }
} }
} else if (pack_type.select == SELECT_CYCLIC) { } else if (pack_type.select == SELECT_EQUAL
size_t total = cards.size() + pack_type.items.size(); || pack_type.select == SELECT_EQUAL_PROPORTIONAL
if (total == 0) return; // prevent div by 0 || pack_type.select == SELECT_EQUAL_NONEMPTY) {
size_t div = requested_copies / total; // equal selection instead of random
size_t rem = requested_copies % total; if (requested_copies == 1) {
for (size_t i = 0 ; i < total ; ++i) { // somewhat of a hack to keep things fair: just pick at random
// how many copies of this card/item do we need? // otherwise we would end up picking the lowest weight item
size_t copies = div + (i < rem ? 1 : 0); generate_one_random(out);
if (i < cards.size()) { } else {
card_copies += copies; // 1. the weights of each item, and of the cards
if (out) out->insert(out->end(), copies, cards[i]); vector<WeightedItem> weighted_items;
} else { FOR_EACH_CONST(item, pack_type.items) {
const PackItemP& item = pack_type.items[i]; WeightedItem wi = {0,0,parent.gen()};
parent.get(item->name).request_copy(copies * item->amount); if (pack_type.select == SELECT_EQUAL_PROPORTIONAL) {
wi.weight = item->weight * parent.get(item->name).total_weight;
} else if (pack_type.select == SELECT_EQUAL_NONEMPTY) {
wi.weight = parent.get(item->name).total_weight > 0 ? item->weight : 0;
} else {
wi.weight = item->weight;
}
weighted_items.push_back(wi);
}
WeightedItem wi = {cards.size(),0,parent.gen()};
weighted_items.push_back(wi);
// 2. divide the requested_copies among the cards and the items, taking the weights into account
weighted_equal_divide(weighted_items, (int)requested_copies);
// 3a. propagate to items
for (size_t j = 0 ; j < pack_type.items.size() ; ++j) {
const PackItem& item = *pack_type.items[j];
PackInstance& i = parent.get(item.name);
i.request_copy(item.amount * weighted_items[j].count);
}
// 3b. pick some cards
int new_card_copies = weighted_items.back().count;
card_copies += new_card_copies;
if (out && !cards.empty()) {
int div = new_card_copies / (int)cards.size();
int rem = new_card_copies % (int)cards.size();
// some copies of all cards
for (int i = 0 ; i < div ; ++i) {
out->insert(out->end(), cards.begin(), cards.end());
}
// pick the remainder at random
for (int i = 0 ; i < rem ; ++i) {
int nr = parent.gen() % cards.size();
out->push_back(cards.at(nr));
}
} }
} }
...@@ -427,12 +457,12 @@ void PackInstance::generate(vector<CardP>* out) { ...@@ -427,12 +457,12 @@ void PackInstance::generate(vector<CardP>* out) {
if (!cards.empty()) { if (!cards.empty()) {
// there is a card, pick it // there is a card, pick it
card_copies += requested_copies; card_copies += requested_copies;
if (out) out->push_back(cards.front()); if (out) out->insert(out->end(), requested_copies, cards.front());
} else { } else {
// pick first nonempty item // pick first nonempty item
FOR_EACH_CONST(item, pack_type.items) { FOR_EACH_CONST(item, pack_type.items) {
PackInstance& i = parent.get(item->name); PackInstance& i = parent.get(item->name);
if (i.count > 0) { if (i.total_weight > 0) {
i.request_copy(requested_copies * item->amount); i.request_copy(requested_copies * item->amount);
break; break;
} }
...@@ -442,6 +472,49 @@ void PackInstance::generate(vector<CardP>* out) { ...@@ -442,6 +472,49 @@ void PackInstance::generate(vector<CardP>* out) {
requested_copies = 0; requested_copies = 0;
} }
void PackInstance::generate_all(vector<CardP>* out, size_t copies) {
card_copies += copies * cards.size();
if (out) {
for (size_t i = 0 ; i < 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(copies * item->amount);
}
}
void PackInstance::generate_one_random(vector<CardP>* out) {
double r = parent.gen() * total_weight / 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_PROPORTIONAL || pack_type.select == SELECT_EQUAL_PROPORTIONAL) {
r -= item->weight * i.total_weight;
} else if (pack_type.select == SELECT_NONEMPTY || pack_type.select == SELECT_EQUAL_NONEMPTY) {
if (i.total_weight > 0) r -= item->weight;
} else {
r -= item->weight;
}
// have we reached the item we were looking for?
if (r < 0) {
i.request_copy(item->amount);
break;
}
}
}
}
// ----------------------------------------------------------------------------- : PackGenerator // ----------------------------------------------------------------------------- : PackGenerator
...@@ -451,6 +524,9 @@ void PackGenerator::reset(const SetP& set, int seed) { ...@@ -451,6 +524,9 @@ void PackGenerator::reset(const SetP& set, int seed) {
max_depth = 0; max_depth = 0;
instances.clear(); instances.clear();
} }
void PackGenerator::reset(int seed) {
gen.seed((unsigned)seed);
}
PackInstance& PackGenerator::get(const String& name) { PackInstance& PackGenerator::get(const String& name) {
PackInstanceP& instance = instances[name]; PackInstanceP& instance = instances[name];
......
...@@ -121,11 +121,13 @@ class PackGenerator; ...@@ -121,11 +121,13 @@ class PackGenerator;
enum PackSelectType enum PackSelectType
{ SELECT_AUTO { SELECT_AUTO
, SELECT_ALL , SELECT_ALL
, SELECT_REPLACE
, SELECT_NO_REPLACE , SELECT_NO_REPLACE
, SELECT_CYCLIC , SELECT_REPLACE
, SELECT_PROPORTIONAL , SELECT_PROPORTIONAL
, SELECT_NONEMPTY , SELECT_NONEMPTY
, SELECT_EQUAL
, SELECT_EQUAL_PROPORTIONAL
, SELECT_EQUAL_NONEMPTY
, SELECT_FIRST , SELECT_FIRST
}; };
...@@ -200,17 +202,23 @@ class PackInstance : public IntrusivePtrBase<PackInstance> { ...@@ -200,17 +202,23 @@ class PackInstance : public IntrusivePtrBase<PackInstance> {
PackGenerator& parent; PackGenerator& parent;
int depth; //< 0 = no items, otherwise 1+max depth of items refered to int depth; //< 0 = no items, otherwise 1+max depth of items refered to
vector<CardP> cards; //< All cards that pass the filter vector<CardP> cards; //< All cards that pass the filter
size_t count; //< Total number of non-empty cards/items
double total_weight; //< Sum of item and card weights double total_weight; //< Sum of item and card weights
size_t requested_copies; //< The requested number of copies of this pack 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 size_t card_copies; //< The number of cards that were chosen to come from this pack
double expected_copies; double expected_copies;
/// Generate some copies of all cards and items
void generate_all(vector<CardP>* out, size_t copies);
/// Generate one card/item chosen at random (using the select type)
void generate_one_random(vector<CardP>* out);
}; };
class PackGenerator { class PackGenerator {
public: public:
/// Reset the generator, possibly switching the set or reseeding /// Reset the generator, possibly switching the set or reseeding
void reset(const SetP& set, int seed); void reset(const SetP& set, int seed);
/// Reset the generator, but not the set
void reset(int seed);
/// Find the PackInstance for the PackType with the given name /// Find the PackInstance for the PackType with the given name
PackInstance& get(const String& name); PackInstance& get(const String& name);
......
...@@ -373,7 +373,7 @@ CustomPackDialog::CustomPackDialog(Window* parent, const SetP& set, const PackTy ...@@ -373,7 +373,7 @@ CustomPackDialog::CustomPackDialog(Window* parent, const SetP& set, const PackTy
} }
void CustomPackDialog::updateTotals() { void CustomPackDialog::updateTotals() {
generator.gen.seed(0); generator.reset(0);
int total_packs = 0; int total_packs = 0;
FOR_EACH(pick,pickers) { FOR_EACH(pick,pickers) {
int copies = pick.value->GetValue(); int copies = pick.value->GetValue();
...@@ -636,7 +636,7 @@ void RandomPackPanel::onPackTypeClick(wxCommandEvent& ev) { ...@@ -636,7 +636,7 @@ void RandomPackPanel::onPackTypeClick(wxCommandEvent& ev) {
void RandomPackPanel::updateTotals() { void RandomPackPanel::updateTotals() {
#if USE_NEW_PACK_SYSTEM #if USE_NEW_PACK_SYSTEM
generator.gen.seed((unsigned)last_seed); generator.reset(last_seed);
#else #else
totals->clear(); totals->clear();
#endif #endif
......
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