• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

APN-Pucky / tyrant_optimize / 26314036851

22 May 2026 09:59PM UTC coverage: 70.517% (+0.2%) from 70.364%
26314036851

Pull #112

github

web-flow
Merge 0a53c6491 into adf7c9377
Pull Request #112: Correct skipped-simulation accounting for DB-served evaluations

18 of 18 new or added lines in 1 file covered. (100.0%)

59 existing lines in 2 files now uncovered.

4805 of 6814 relevant lines covered (70.52%)

8409245.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.36
/algorithms.cpp
1
#include <iostream>
2
#include <vector>
3

4
#include "algorithms_util.h"
5

6
using namespace tuo;
7

8
inline unsigned skipped_simulations_increment(Process& proc, unsigned num_iterations)
878✔
9
{
10
        if (num_iterations == 0 || !use_db_load)
878✔
11
        {
12
                return num_iterations;
13
        }
14
        std::vector<std::array<std::string, 3>> const vhashes = proc.hashes();
51✔
15
        EvaluatedResults db_results{EvaluatedResults::first_type(vhashes.size()), 0};
51✔
16
        if (!proc.check_db(vhashes, num_iterations, db_results))
51✔
17
        {
18
                return 0;
51✔
19
        }
20
        return num_iterations;
21
}
51✔
22

23
inline bool try_improve_deck(Deck* d1, unsigned from_slot, unsigned to_slot, const Card* card_candidate,
2,059✔
24
                const Card*& best_commander, const Card*& best_alpha_dominion, std::vector<const Card*>& best_cards,
25
                FinalResults<long double>& best_score, unsigned& best_gap, std::string& best_deck,
26
                std::unordered_map<std::string, EvaluatedResults>& evaluated_decks, EvaluatedResults& zero_results,
27
                unsigned long& skipped_simulations, Process& proc, bool print
28
#ifndef NQUEST
29
                , Quest & quest
30
#endif
31
        )
32
{
33
        unsigned deck_cost(0);
2,059✔
34
        std::vector<std::pair<signed, const Card *>> cards_out, cards_in;
2,059✔
35
        std::mt19937& re = proc.threads_data[0]->re;
2,059✔
36

37
        // setup best deck
38
        d1->commander = best_commander;
2,059✔
39
        d1->alpha_dominion = best_alpha_dominion;
2,059✔
40
        d1->cards = best_cards;
2,059✔
41

42
        // try to adjust the deck
43
        if (!adjust_deck(d1, from_slot, to_slot, card_candidate, fund, re, deck_cost, cards_out, cards_in))
2,059✔
44
        { return false; }
45

46
        // check gap
47
        unsigned new_gap = check_requirement(d1, requirement
1,573✔
48
#ifndef NQUEST
49
                        , quest
50
#endif
51
                        );
52
        if ((new_gap > 0) && (new_gap >= best_gap))
1,573✔
53
        { return false; }
54

55
        // check previous simulations
56
        auto && cur_deck = d1->hash();
1,573✔
57
        auto && emplace_rv = evaluated_decks.insert({cur_deck, zero_results});
3,146✔
58
        auto & prev_results = emplace_rv.first->second;
1,573✔
59
        if (!emplace_rv.second)
1,573✔
60
        {
61
                skipped_simulations += skipped_simulations_increment(proc, prev_results.second);
499✔
62
        }
63

64
        // Evaluate new deck
65
        auto compare_results = proc.compare(best_score.n_sims, prev_results, best_score);
1,573✔
66
        auto current_score = compute_score(compare_results, proc.factors);
1,573✔
67

68
        // Is it better ?
69
        if (new_gap < best_gap || current_score.points > best_score.points + min_increment_of_score)
1,573✔
70
        {
71
                // Then update best score/slot, print stuff
72
                if(print)std::cout << "Deck improved: " << d1->hash() << ": " << card_slot_id_names(cards_out) << " -> " << card_slot_id_names(cards_in) << ": ";
207✔
73
                best_gap = new_gap;
79✔
74
                best_score = current_score;
79✔
75
                best_deck = cur_deck;
79✔
76
                best_commander = d1->commander;
79✔
77
                best_alpha_dominion = d1->alpha_dominion;
79✔
78
                best_cards = d1->cards;
79✔
79
                if(print)print_score_info(compare_results, proc.factors);
79✔
80
                if(print)print_deck_inline(deck_cost, best_score, d1);
32✔
81
                return true;
79✔
82
        }
83

84
        return false;
85
}
2,059✔
86
//------------------------------------------------------------------------------
87
/*
88
 * Calc value of current set deck in d1 (proc.your_decks[0])
89
 */
90
inline FinalResults<long double> fitness(Deck* d1,
1,055✔
91
                FinalResults<long double>& best_score,
92
                std::unordered_map<std::string, EvaluatedResults>& evaluated_decks, EvaluatedResults& zero_results,
93
                unsigned long& skipped_simulations, Process& proc, bool compare = false)
94
{
95

96
        // check previous simulations
97
        auto && cur_deck = d1->hash();
1,055✔
98
        //std::cout << "Deck hash: " << d1->hash() << " with ";
99
        auto && emplace_rv = evaluated_decks.insert({cur_deck, zero_results});
2,110✔
100
        auto & prev_results = emplace_rv.first->second;
1,055✔
101
        if (!emplace_rv.second)
1,055✔
102
        {
103
                skipped_simulations += skipped_simulations_increment(proc, prev_results.second);
377✔
104
        }
105

106
        // Evaluate new deck
107
        if (compare) {
1,055✔
108
                auto compare_results= proc.compare(best_score.n_sims, prev_results,best_score);
720✔
109
                auto current_score = compute_score(compare_results, proc.factors);
720✔
110
                return current_score;
720✔
111
        }
720✔
112
        else
113
        {
114
                auto compare_results= proc.evaluate(best_score.n_sims, prev_results);
335✔
115
                auto current_score = compute_score(compare_results, proc.factors);
335✔
116

117
                //best_score = current_score;
118
                //auto best_deck = d1->clone();
119
                //print_score_info(compare_results, proc.factors);
120
                //print_deck_inline(get_deck_cost(best_deck), best_score, best_deck);
121
                return current_score;
335✔
122
        }
335✔
123
}
1,055✔
124
//------------------------------------------------------------------------------
125
Deck* filter_best_deck(std::vector<Deck*> your_decks, Deck* d1,
3✔
126
                FinalResults<long double>& best_score,
127
                std::unordered_map<std::string, EvaluatedResults>& evaluated_decks, EvaluatedResults& zero_results,
128
                unsigned long& skipped_simulations, Process& proc) {
129
        Deck * cur_return = your_decks[0];
3✔
130
        FinalResults<long double> cur_score;
3✔
131
        for(unsigned i=1;i < your_decks.size();++i) //start with 1 since first always is simmed already
3✔
132
        {
UNCOV
133
                copy_deck(your_decks[i],d1);
×
UNCOV
134
                cur_score = fitness(d1, best_score, evaluated_decks, zero_results, skipped_simulations, proc,true);
×
UNCOV
135
                if(cur_score.points > best_score.points)
×
136
                {
UNCOV
137
                        cur_return = your_decks[i];
×
UNCOV
138
                        best_score = cur_score;
×
139

UNCOV
140
                        std::cout << "Deck improved: " << d1->hash() <<":";
×
UNCOV
141
                        print_deck_inline(get_deck_cost(d1), best_score,d1);
×
142
                }
143
        }
144
        return cur_return;
3✔
145
}
146

147
//------------------------------------------------------------------------------
148
DeckResults hill_climbing(unsigned num_min_iterations, unsigned num_iterations, std::vector<Deck*> your_decks , Process& proc, Requirement & requirement
2✔
149
#ifndef NQUEST
150
                , Quest & quest
151
#endif
152
                )
153
{
154
        Deck * d1 = proc.your_decks[0];
2✔
155
        EvaluatedResults zero_results = { EvaluatedResults::first_type(proc.enemy_decks.size()), 0 };
2✔
156
        std::string best_deck = d1->hash();
2✔
157
        std::unordered_map<std::string, EvaluatedResults> evaluated_decks{{best_deck, zero_results}};
6✔
158
        EvaluatedResults& results = proc.evaluate(num_min_iterations, evaluated_decks.begin()->second);
2✔
159
        print_score_info(results, proc.factors);
2✔
160
        FinalResults<long double> best_score = compute_score(results, proc.factors);
2✔
161
        unsigned long skipped_simulations = 0;
2✔
162

163
        // use the best deck from all passed decks
164
        copy_deck(filter_best_deck(your_decks, d1, best_score, evaluated_decks, zero_results, skipped_simulations, proc),d1);
2✔
165

166
        // update freezed_cards
167
        freezed_cards = std::min<unsigned>(opt_freezed_cards, d1->cards.size());
2✔
168
        const Card* best_commander = d1->commander;
2✔
169
        const Card* best_alpha_dominion = d1->alpha_dominion;
2✔
170
        std::vector<const Card*> best_cards = d1->cards;
2✔
171
        unsigned deck_cost = get_deck_cost(d1);
2✔
172
        fund = std::max(fund, deck_cost);
2✔
173
        print_deck_inline(deck_cost, best_score, d1,true);
2✔
174
        std::mt19937& re = proc.threads_data[0]->re;
2✔
175
        unsigned best_gap = check_requirement(d1, requirement
2✔
176
#ifndef NQUEST
177
                        , quest
178
#endif
179
                        );
2✔
180
        bool is_random = (d1->strategy == DeckStrategy::random) || (d1->strategy == DeckStrategy::flexible);
2✔
181
        bool deck_has_been_improved = true;
2✔
182
        std::vector<const Card*> commander_candidates;
2✔
183
        std::vector<const Card*> alpha_dominion_candidates;
2✔
184
        std::vector<const Card*> card_candidates;
2✔
185

186
        auto mixed_candidates = get_candidate_lists(proc);
2✔
187
        commander_candidates = mixed_candidates.at(0);
2✔
188
        alpha_dominion_candidates = mixed_candidates.at(1);
2✔
189
        card_candidates = mixed_candidates.at(2);
2✔
190
        // add current alpha dominion to candidates if necessary
191
        // or setup first candidate into the deck if no alpha dominion defined
192
        if (use_owned_dominions)
2✔
193
        {
194
                if (best_alpha_dominion)
2✔
195
                {
UNCOV
196
                        if (!std::count(alpha_dominion_candidates.begin(), alpha_dominion_candidates.end(), best_alpha_dominion))
×
197
                        {
UNCOV
198
                                alpha_dominion_candidates.emplace_back(best_alpha_dominion);
×
199
                        }
200
                }
201
                else if (!alpha_dominion_candidates.empty())
2✔
202
                {
203
                        best_alpha_dominion = d1->alpha_dominion = alpha_dominion_candidates[0];
2✔
204
                }
205
                if (debug_print > 0)
2✔
206
                {
UNCOV
207
                        for (const Card* dom_card : alpha_dominion_candidates)
×
208
                        {
UNCOV
209
                                std::cout << " ** next Alpha Dominion candidate: " << dom_card->m_name
×
UNCOV
210
                                        << " ($: " << alpha_dominion_cost(dom_card) << ")" << std::endl;
×
211
                        }
212
                }
213
                if (!best_alpha_dominion && owned_alpha_dominion)
2✔
214
                {
UNCOV
215
                        best_alpha_dominion = owned_alpha_dominion;
×
UNCOV
216
                        std::cout << "Setting up owned Alpha Dominion into a deck: " << best_alpha_dominion->m_name << std::endl;
×
217
                }
218
        }
219
        //std::reverse(card_candidates.begin(), card_candidates.end());
220

221

222

223
        // << main climbing loop >>
224
        for (unsigned from_slot(freezed_cards), dead_slot(freezed_cards); ;
2✔
225
                        from_slot = std::max(freezed_cards, (from_slot + 1) % std::min<unsigned>(max_deck_len, best_cards.size() + 1)))
36✔
226
        {
227
                if(is_timeout_reached()){ break;}
38✔
228
                if (deck_has_been_improved)
38✔
229
                {
230
                        dead_slot = from_slot;
231
                        deck_has_been_improved = false;
232
                }
233
                else if (from_slot == dead_slot || best_score.points - target_score > -1e-9)
25✔
234
                {
235
                        if (best_score.n_sims >= num_iterations || best_gap > 0)
4✔
236
                        { break; } // exit main climbing loop
237
                        auto & prev_results = evaluated_decks[best_deck];
2✔
238
                        skipped_simulations += skipped_simulations_increment(proc, prev_results.second);
2✔
239
                        // Re-evaluate the best deck
240
                        d1->commander = best_commander;
2✔
241
                        d1->alpha_dominion = best_alpha_dominion;
2✔
242
                        d1->cards = best_cards;
2✔
243
                        auto evaluate_result = proc.evaluate(std::min(prev_results.second * iterations_multiplier, num_iterations), prev_results);
2✔
244
                        best_score = compute_score(evaluate_result, proc.factors);
2✔
245
                        print_score_info(evaluate_result, proc.factors);
2✔
246
                        dead_slot = from_slot;
2✔
247
                }
2✔
248
                if (best_score.points - target_score > -1e-9)
36✔
249
                { continue; }
2✔
250

251
                // commander
252
                if (requirement.num_cards.count(best_commander) == 0)
34✔
253
                {
254
                        // << commander candidate loop >>
255
                        for (const Card* commander_candidate: commander_candidates)
332✔
256
                        {
257
                                if (best_score.points - target_score > -1e-9)
299✔
258
                                { break; }
259
                                if (commander_candidate == best_commander)
298✔
260
                                { continue; }
31✔
261
                                deck_has_been_improved |= try_improve_deck(d1, -1, -1, commander_candidate,
267✔
262
                                                best_commander, best_alpha_dominion, best_cards, best_score, best_gap, best_deck,
263
                                                evaluated_decks, zero_results, skipped_simulations, proc,true
264
#ifndef NQUEST
265
                        , quest
266
#endif
267
                        );
268
                        }
269
                        // Now that all commanders are evaluated, take the best one
270
                        d1->commander = best_commander;
34✔
271
                        d1->alpha_dominion = best_alpha_dominion;
34✔
272
                        d1->cards = best_cards;
34✔
273
                }
274

275
                // alpha dominion
276
                if (use_owned_dominions && !alpha_dominion_candidates.empty())
34✔
277
                {
278
                        // << alpha dominion candidate loop >>
279
                        for (const Card* alpha_dominion_candidate: alpha_dominion_candidates)
133✔
280
                        {
281
                                if (best_score.points - target_score > -1e-9)
100✔
282
                                { break; }
283
                                if (alpha_dominion_candidate == best_alpha_dominion)
99✔
284
                                { continue; }
33✔
285
                                deck_has_been_improved |= try_improve_deck(d1, -1, -1, alpha_dominion_candidate,
66✔
286
                                                best_commander, best_alpha_dominion, best_cards, best_score, best_gap, best_deck,
287
                                                evaluated_decks, zero_results, skipped_simulations, proc,true
288
#ifndef NQUEST
289
                        , quest
290
#endif
291
                        );
292
                        }
293
                        // Now that all alpha dominions are evaluated, take the best one
294
                        d1->commander = best_commander;
34✔
295
                        d1->alpha_dominion = best_alpha_dominion;
34✔
296
                        d1->cards = best_cards;
34✔
297
                }
298

299
                // shuffle candidates
300
                std::shuffle(card_candidates.begin(), card_candidates.end(), re);
34✔
301

302
                // << card candidate loop >>
303
                //for (const Card* card_candidate: card_candidates)
304
                for (auto it = card_candidates.begin(); it != card_candidates.end();++it)
804✔
305
                {
306
                        const Card* card_candidate = *it;
772✔
307
                        for (unsigned to_slot(is_random ? from_slot : card_candidate ? freezed_cards : (best_cards.size() - 1));
1,544✔
308
                                        to_slot < (is_random ? (from_slot + 1) : (best_cards.size() + (from_slot < best_cards.size() ? 0 : 1)));
1,544✔
309
                                        ++ to_slot)
310
                        {
311
                                if (card_candidate ?
772✔
312
                                                (from_slot < best_cards.size() && (from_slot == to_slot && card_candidate == best_cards[to_slot])) // 2 Omega -> 2 Omega
740✔
313
                                                :
314
                                                (from_slot == best_cards.size())) // void -> void
32✔
315
                                { continue; }
26✔
316
                                deck_has_been_improved |= try_improve_deck(d1, from_slot, to_slot, card_candidate,
746✔
317
                                                best_commander, best_alpha_dominion, best_cards, best_score, best_gap, best_deck,
318
                                                evaluated_decks, zero_results, skipped_simulations, proc,true
319
#ifndef NQUEST
320
                        , quest
321
#endif
322
                        );
323
                        }
324
                        if (best_score.points - target_score > -1e-9)
772✔
325
                        { break; }
326

327
                }
328
        }
36✔
329
        d1->commander = best_commander;
2✔
330
        d1->alpha_dominion = best_alpha_dominion;
2✔
331
        d1->cards = best_cards;
2✔
332
        unsigned simulations = 0;
2✔
333
        for (auto evaluation: evaluated_decks)
1,112✔
334
        { simulations += evaluation.second.second; }
1,110✔
335
        std::cout << "Evaluated " << evaluated_decks.size() << " decks (" << simulations << " + " << skipped_simulations << " simulations)." << std::endl;
2✔
336
        print_sim_card_values(d1,proc,num_iterations);
2✔
337
        std::cout << "Optimized Deck: ";
2✔
338
        print_deck_inline(get_deck_cost(d1), best_score, d1,true);
2✔
339
        print_upgraded_cards(d1);
2✔
340
        return std::make_pair(d1->clone(),best_score);
2✔
341
}
4✔
342

343

344

345

346
inline long double acceptanceProbability(long double old_score, long double new_score, long double temperature)
145✔
347
{
348
        if(new_score > old_score)
145✔
349
        {
350
                return 1;
351
        }
352
        //100/max_score normalizes the acceptance probability with the used mode/score-range
353
        //1000 is factor to set the temperature lower (can be changed indirect by setting begin-temperature and its decrease)
354
        return exp(((new_score-old_score)/temperature*1000*100)/max_possible_score[(size_t)optimization_mode]);
135✔
355
}
356

357
DeckResults simulated_annealing(unsigned num_min_iterations, unsigned num_iterations, std::vector<Deck*> your_decks, Process& proc, Requirement & requirement
1✔
358
#ifndef NQUEST
359
                , Quest & quest
360
#endif
361
                )
362
{
363
        Deck* cur_deck = proc.your_decks[0];
1✔
364
        EvaluatedResults zero_results = { EvaluatedResults::first_type(proc.enemy_decks.size()), 0 };
1✔
365
        //std::string best_deck = d1->hash();
366
        std::unordered_map<std::string, EvaluatedResults> evaluated_decks{{cur_deck->hash(), zero_results}};
4✔
367
        EvaluatedResults& results = proc.evaluate(num_min_iterations, evaluated_decks.begin()->second);
1✔
368
        print_score_info(results, proc.factors);
1✔
369
        FinalResults<long double> best_score = compute_score(results, proc.factors);
1✔
370
        //const Card* best_commander = d1->commander;
371
        //const Card* best_alpha_dominion = cur_deck->alpha_dominion;
372
        //std::vector<const Card*> best_cards = cur_deck->cards;
373
        unsigned long skipped_simulations = 0;
1✔
374

375

376
        // use the best deck from all passed decks
377
        copy_deck(filter_best_deck(your_decks, cur_deck, best_score, evaluated_decks, zero_results, skipped_simulations, proc),cur_deck);
1✔
378

379
        // update freezed_cards
380
        freezed_cards = std::min<unsigned>(opt_freezed_cards, cur_deck->cards.size());
1✔
381

382
        unsigned deck_cost = get_deck_cost(cur_deck);
1✔
383
        fund = std::max(fund, deck_cost);
1✔
384
        print_deck_inline(deck_cost, best_score, cur_deck,true);
1✔
385
        std::mt19937& re = proc.threads_data[0]->re;
1✔
386
        unsigned cur_gap = check_requirement(cur_deck, requirement
1✔
387
#ifndef NQUEST
388
                        , quest
389
#endif
390
                        );
391

392
        bool is_random = (cur_deck->strategy == DeckStrategy::random) || (cur_deck->strategy == DeckStrategy::flexible);
1✔
393
        std::vector<const Card*> all_candidates;
1✔
394

395
        auto mixed_candidates = get_candidate_lists(proc);
1✔
396
        all_candidates.reserve(mixed_candidates.at(0).size()+mixed_candidates.at(1).size()+mixed_candidates.at(2).size());
1✔
397
        all_candidates.insert(all_candidates.end(), std::make_move_iterator(mixed_candidates.at(0).begin()),std::make_move_iterator(mixed_candidates.at(0).end()));
1✔
398
        all_candidates.insert(all_candidates.end(), std::make_move_iterator(mixed_candidates.at(1).begin()),std::make_move_iterator(mixed_candidates.at(1).end()));
1✔
399
        all_candidates.insert(all_candidates.end(), std::make_move_iterator(mixed_candidates.at(2).begin()),std::make_move_iterator(mixed_candidates.at(2).end()));
1✔
400
        //clear
401
        mixed_candidates.at(0).clear(); mixed_candidates.at(0).shrink_to_fit();
2✔
402
        mixed_candidates.at(1).clear(); mixed_candidates.at(1).shrink_to_fit();
2✔
403
        mixed_candidates.at(2).clear(); mixed_candidates.at(2).shrink_to_fit();
2✔
404
        mixed_candidates.clear();mixed_candidates.shrink_to_fit();
2✔
405

406
        // add current alpha dominion to candidates if necessary
407
        // or setup first candidate into the deck if no alpha dominion defined
408
        if (use_owned_dominions)
1✔
409
        {
410
                if (cur_deck->alpha_dominion)
1✔
411
                {
UNCOV
412
                        if (!std::count(all_candidates.begin(), all_candidates.end(), cur_deck->alpha_dominion))
×
413
                        {
UNCOV
414
                                all_candidates.emplace_back(cur_deck->alpha_dominion);
×
415
                        }
416
                }
417
                if (!cur_deck->alpha_dominion && owned_alpha_dominion)
1✔
418
                {
419
                        cur_deck->alpha_dominion = owned_alpha_dominion;
1✔
420
                        std::cout << "Setting up owned Alpha Dominion into a deck: " << cur_deck->alpha_dominion->m_name << std::endl;
1✔
421
                }
422
        }
423

424
        Deck* prev_deck = cur_deck->clone();
1✔
425
        Deck* best_deck = cur_deck->clone();
1✔
426

427
        FinalResults<long double> prev_score = best_score;
1✔
428
        FinalResults<long double> cur_score = best_score;
1✔
429

430
        unsigned best_gap = cur_gap;
1✔
431

432
        deck_cost = 0;
1✔
433

434
        unsigned from_slot(freezed_cards);
1✔
435
        unsigned from_slot_tmp(freezed_cards);
1✔
436
        unsigned to_slot(1);
1✔
437

438
        if(debug_print >0)std::cout << "Starting Anneal" << std::endl;
1✔
439
        while(temperature > 1 && !(best_score.points - target_score > -1e-9 || is_timeout_reached()))
218✔
440
        {
441
                cur_deck->commander = prev_deck->commander;
217✔
442
                cur_deck->alpha_dominion = prev_deck->alpha_dominion;
217✔
443
                cur_deck->cards = prev_deck->cards;
217✔
444
                from_slot = std::max(freezed_cards, (from_slot+1) % std::min<unsigned>(max_deck_len, cur_deck->cards.size() +1));
217✔
445
                const Card* candidate = all_candidates.at(std::uniform_int_distribution<unsigned>(0,all_candidates.size()-1)(re));
217✔
446

447

448
                if((!candidate || (candidate->m_category == CardCategory::normal && candidate->m_type != CardType::commander && candidate->m_category != CardCategory::dominion_alpha)))
217✔
449
                {
450

451
                        to_slot = std::uniform_int_distribution<unsigned>(is_random ? from_slot : candidate ? freezed_cards : (cur_deck->cards.size() -1),(is_random ? (from_slot+1) : (cur_deck->cards.size() + ( from_slot < cur_deck->cards.size() ? 0 : 1)))-1)(re);
143✔
452
                        if(candidate ?
143✔
453
                                        (from_slot < cur_deck->cards.size() && (from_slot == to_slot && candidate == cur_deck->cards[to_slot]))
139✔
454
                                        :
455
                                        (from_slot == cur_deck->cards.size()))
4✔
456
                        {
457
                                continue;
72✔
458
                        }
459
                        from_slot_tmp = from_slot;
460
                }
461
                else if(candidate->m_type == CardType::commander && requirement.num_cards.count(cur_deck->commander) == 0)
74✔
462
                {
463
                        cur_deck->commander = candidate;
53✔
464
                        from_slot_tmp = -1;
53✔
465
                        to_slot = -1;
53✔
466
                }
467
                else if(candidate->m_category == CardCategory::dominion_alpha && use_owned_dominions)
21✔
468
                {
469
                        cur_deck->alpha_dominion = candidate;
21✔
470
                        from_slot_tmp = -1;
21✔
471
                        to_slot = -1;
21✔
472
                }
473
                else{
UNCOV
474
                        continue;
×
475
                }
476

477
                std::vector<std::pair<signed, const Card * >> cards_out, cards_in;
211✔
478
                if (!adjust_deck(cur_deck, from_slot_tmp, to_slot, candidate, fund, re, deck_cost, cards_out, cards_in))
211✔
479
                { continue;}
54✔
480
                cur_gap = check_requirement(cur_deck, requirement
157✔
481
#ifndef NQUEST
482
                                , quest
483
#endif
484
                                );
485
                if ((cur_gap > 0) && (cur_gap >= best_gap))
157✔
UNCOV
486
                { continue; }
×
487

488
                //same deck skip
489
                if(cur_deck->hash().compare(prev_deck->hash())==0)continue;
157✔
490

491

492

493
                cur_score = fitness(cur_deck, best_score, evaluated_decks, zero_results, skipped_simulations, proc);
145✔
494

495
                if(acceptanceProbability(prev_score.points, cur_score.points , temperature) > std::uniform_real_distribution<double>(0,1)(re))
145✔
496
                {
497
                        if(cur_score.points > best_score.points)
12✔
498
                        {
499
                                best_score = cur_score;
10✔
500
                                best_deck = cur_deck->clone();
10✔
501
                                best_gap = cur_gap;
10✔
502
                                std::cout << "Deck improved: " << best_deck->hash() << ": (temp=" << temperature << ") :";
20✔
503
                                print_deck_inline(get_deck_cost(best_deck), best_score, best_deck);
10✔
504
                        }
505
                        if(debug_print>0)std::cout << "UPDATED DECK: " << cur_deck->hash() << ": (temp=" << temperature << ") :";
12✔
506
                        if(debug_print>0)print_deck_inline(get_deck_cost(cur_deck), cur_score, cur_deck);
12✔
507
                        prev_score = cur_score;
12✔
508
                        prev_deck = cur_deck->clone();
12✔
509
                }
510
                temperature *=1-coolingRate;
145✔
511
        }
211✔
512
        unsigned simulations = 0;
1✔
513
        for (auto evaluation: evaluated_decks)
215✔
514
        { simulations += evaluation.second.second; }
214✔
515
        std::cout << "Evaluated " << evaluated_decks.size() << " decks (" << simulations << " + " << skipped_simulations << " simulations)." << std::endl;
1✔
516
        print_sim_card_values(best_deck,proc,num_iterations);
1✔
517
        std::cout << "Optimized Deck: ";
1✔
518
        print_deck_inline(get_deck_cost(best_deck), best_score, best_deck,true);
1✔
519
        print_upgraded_cards(best_deck);
1✔
520
        return std::make_pair(best_deck->clone(),best_score);
1✔
521
}
2✔
522

523

524

525

526
void crossover(Deck* src1,Deck* src2, Deck* cur_deck, std::mt19937& re,unsigned best_gap,std::unordered_map<std::string, EvaluatedResults>& evaluated_decks
307✔
527
#ifndef NQUEST
528
                , Quest & quest
529
#endif
530
)
531
{
532
        cur_deck->commander = std::uniform_int_distribution<unsigned>(0, 1)(re)?src1->commander:src2->commander;
307✔
533
        cur_deck->alpha_dominion = std::uniform_int_distribution<unsigned>(0, 1)(re)?src1->alpha_dominion:src2->alpha_dominion;
307✔
534
        bool finished = false;
307✔
535
        unsigned itr = 0;
307✔
536
        while(!finished && itr < 100) //todo opt
11,853✔
537
        {
538
                itr++;
11,546✔
539
                cur_deck->cards.clear();
11,546✔
540
                for(unsigned it =0; it < std::max(src1->cards.size(),src2->cards.size());it++)
150,415✔
541
                {
542
                        if(src1->cards.size() <=it)
114,097✔
543
                        {cur_deck->cards.push_back(src2->cards[it]);}
2,252✔
544
                        else if(src2->cards.size() <=it)
111,845✔
545
                        {cur_deck->cards.push_back(src1->cards[it]);}
8,601✔
546
                        else
547
                        {
548
                                cur_deck->cards.push_back(std::uniform_int_distribution<unsigned>(0, 1)(re)?src1->cards[it]:src2->cards[it]);
103,244✔
549
                        }
550
                }
551
                if(!valid_deck(cur_deck)) {continue;} //repeat
11,546✔
552

553
                if(evaluated_decks.count(cur_deck->hash())){continue;} // deck already simmed
12,646✔
554
                unsigned cur_gap = check_requirement(cur_deck, requirement
219✔
555
#ifndef NQUEST
556
                                , quest
557
#endif
558
                                );
559
                if ((cur_gap > 0) && (cur_gap >= best_gap))
219✔
UNCOV
560
                { continue; }
×
561
                finished = true; // exit while
562
        }
563
        if(!finished) copy_deck(std::uniform_int_distribution<unsigned>(0, 1)(re)?src1:src2,cur_deck);
350✔
564
}
307✔
565

566
void mutate(Deck* src, Deck* cur_deck, std::vector<const Card*> all_candidates, std::mt19937& re,unsigned best_gap,std::unordered_map<std::string, EvaluatedResults>& evaluated_decks
691✔
567
#ifndef NQUEST
568
                , Quest & quest
569
#endif
570
)
571
{
572
        copy_deck(src,cur_deck);
691✔
573

574
        bool is_random = (cur_deck->strategy == DeckStrategy::random) || (cur_deck->strategy == DeckStrategy::flexible); //should be same for all decks from input!?
691✔
575

576
        unsigned deck_cost = 0;
691✔
577

578
        unsigned from_slot(freezed_cards);
691✔
579
        unsigned from_slot_tmp(freezed_cards);
691✔
580
        unsigned to_slot(1);
691✔
581

582
        bool finished = false;
691✔
583
        unsigned itr=0;
691✔
584
        while(!finished && itr < 100) // todo opt
1,806✔
585
        {
586
                itr++;
1,115✔
587
                copy_deck(src,cur_deck);
1,115✔
588

589
                from_slot = std::uniform_int_distribution<unsigned>(1,std::min<unsigned>(max_deck_len-1, cur_deck->cards.size()))(re);
1,115✔
590

591
                const Card* candidate = all_candidates.at(std::uniform_int_distribution<unsigned>(0,all_candidates.size()-1)(re));
1,115✔
592

593

594
                if((!candidate || (candidate->m_category == CardCategory::normal && candidate->m_type != CardType::commander && candidate->m_category != CardCategory::dominion_alpha)))
1,115✔
595
                {
596

597
                        to_slot = std::uniform_int_distribution<unsigned>(is_random ? from_slot : candidate ? freezed_cards : (cur_deck->cards.size() -1),(is_random ? (from_slot+1) : (cur_deck->cards.size() + ( from_slot < cur_deck->cards.size() ? 0 : 1)))-1)(re);
752✔
598
                        if(candidate ?
752✔
599
                                        (from_slot < cur_deck->cards.size() && (from_slot == to_slot && candidate == cur_deck->cards[to_slot]))
715✔
600
                                        :
601
                                        (from_slot == cur_deck->cards.size()))
37✔
602
                        {
603
                                continue;
424✔
604
                        }
605
                        from_slot_tmp = from_slot;
606
                }
607
                else if(candidate->m_type == CardType::commander && requirement.num_cards.count(cur_deck->commander) == 0)
363✔
608
                {
609
                        cur_deck->commander = candidate;
267✔
610
                        from_slot_tmp = -1;
267✔
611
                        to_slot = -1;
267✔
612
                }
613
                else if(candidate->m_category == CardCategory::dominion_alpha && use_owned_dominions)
96✔
614
                {
615
                        cur_deck->alpha_dominion = candidate;
96✔
616
                        from_slot_tmp = -1;
96✔
617
                        to_slot = -1;
96✔
618
                }
619
                else{
UNCOV
620
                        continue;
×
621
                }
622

623
                std::vector<std::pair<signed, const Card * >> cards_out, cards_in;
1,091✔
624
                if (!adjust_deck(cur_deck, from_slot_tmp, to_slot, candidate, fund, re, deck_cost, cards_out, cards_in))
1,091✔
625
                { continue;}
269✔
626

627
                if(evaluated_decks.count(cur_deck->hash()))
1,644✔
628
                {continue;} // deck already simmed
131✔
629
                if(!valid_deck(cur_deck)) {continue;} //repeat
691✔
630
                unsigned cur_gap = check_requirement(cur_deck, requirement
691✔
631
#ifndef NQUEST
632
                                , quest
633
#endif
634
                                );
635
                if ((cur_gap > 0) && (cur_gap >= best_gap))
691✔
UNCOV
636
                { continue; }
×
637
                finished = true; // exit while
691✔
638
        }
1,091✔
639
        if(!finished) copy_deck(src,cur_deck);
691✔
640
}
691✔
641
DeckResults genetic_algorithm(unsigned num_min_iterations, unsigned num_iterations, std::vector<Deck*> your_decks, Process& proc, Requirement & requirement
1✔
642
#ifndef NQUEST
643
                , Quest & quest
644
#endif
645
                )
646
{
647
        //std::cerr << "START GENETIC" << std::endl;
648
        if(pool_size==0){
1✔
649
                if(your_decks.size()>min_pool_size) { //
1✔
UNCOV
650
                        pool_size = your_decks.size();
×
651
                }
652
                else {
653
                        pool_size = min_pool_size;
1✔
654
                }
655
        }
656
        unsigned pool_cross = pool_size*opt_pool_cross/(opt_pool_mutate+opt_pool_cross+opt_pool_keep);
1✔
657
        unsigned pool_keep = pool_size*opt_pool_keep/(opt_pool_mutate+opt_pool_cross+opt_pool_keep);
1✔
658
        unsigned pool_mutate = pool_size-pool_cross-pool_keep;
1✔
659

660
        //your_decks.size();
661
        std::vector<std::pair<Deck*,FinalResults<long double>>> pool;
1✔
662
        Deck* cur_deck = proc.your_decks[0];
1✔
663

664

665

666
        EvaluatedResults zero_results = { EvaluatedResults::first_type(proc.enemy_decks.size()), 0 };
1✔
667

668
        std::unordered_map<std::string, EvaluatedResults> evaluated_decks{{cur_deck->hash(), zero_results}};
4✔
669
        EvaluatedResults& results = proc.evaluate(num_min_iterations, evaluated_decks.begin()->second);
1✔
670
        print_score_info(results, proc.factors);
1✔
671
        FinalResults<long double> best_score = compute_score(results, proc.factors); //init sim, todo remove
1✔
672

673
        unsigned deck_cost = get_deck_cost(cur_deck);
1✔
674
        fund = std::max(fund, deck_cost);
1✔
675
        print_deck_inline(deck_cost, best_score, cur_deck,true);
1✔
676
        std::mt19937& re = proc.threads_data[0]->re;
1✔
677
        unsigned cur_gap = check_requirement(cur_deck, requirement
1✔
678
#ifndef NQUEST
679
                        , quest
680
#endif
681
                        );
682

683
        unsigned long skipped_simulations = 0;
1✔
684
        std::vector<const Card*> all_candidates;
1✔
685

686
        auto mixed_candidates = get_candidate_lists(proc);
1✔
687
        all_candidates.reserve(mixed_candidates.at(0).size()+mixed_candidates.at(1).size()+mixed_candidates.at(2).size());
1✔
688
        all_candidates.insert(all_candidates.end(), std::make_move_iterator(mixed_candidates.at(0).begin()),std::make_move_iterator(mixed_candidates.at(0).end()));
1✔
689
        all_candidates.insert(all_candidates.end(), std::make_move_iterator(mixed_candidates.at(1).begin()),std::make_move_iterator(mixed_candidates.at(1).end()));
1✔
690
        all_candidates.insert(all_candidates.end(), std::make_move_iterator(mixed_candidates.at(2).begin()),std::make_move_iterator(mixed_candidates.at(2).end()));
1✔
691
        //clear
692
        mixed_candidates.at(0).clear(); mixed_candidates.at(0).shrink_to_fit();
2✔
693
        mixed_candidates.at(1).clear(); mixed_candidates.at(1).shrink_to_fit();
2✔
694
        mixed_candidates.at(2).clear(); mixed_candidates.at(2).shrink_to_fit();
2✔
695
        mixed_candidates.clear();mixed_candidates.shrink_to_fit();
2✔
696

697
        // add current alpha dominion to candidates if necessary
698
        // or setup first candidate into the deck if no alpha dominion defined
699

700
        for( auto tmp_deck : your_decks) // check all decks for owned dominions
2✔
701
        {
702
                if (use_owned_dominions)
1✔
703
                {
704
                        if (tmp_deck->alpha_dominion)
1✔
705
                        {
UNCOV
706
                                if (!std::count(all_candidates.begin(), all_candidates.end(), tmp_deck->alpha_dominion))
×
707
                                {
UNCOV
708
                                        all_candidates.emplace_back(tmp_deck->alpha_dominion);
×
709
                                }
710
                        }
711
                        if (!tmp_deck->alpha_dominion && owned_alpha_dominion)
1✔
712
                        {
713
                                tmp_deck->alpha_dominion = owned_alpha_dominion;
1✔
714
                                std::cout << "Setting up owned Alpha Dominion into a deck: " << tmp_deck->alpha_dominion->m_name << std::endl;
1✔
715
                        }
716
                }
717
        }
718
        Deck* best_deck = cur_deck->clone();
1✔
719
        FinalResults<long double> cur_score = best_score;
1✔
720
        unsigned best_gap = cur_gap;
1✔
721

722
        //fill pool
723
        if(your_decks.size()>pool_size) your_decks.resize(pool_size); //TODO this drops potential better overflowing decks?!
1✔
724
        for ( unsigned it =your_decks.size(); it < pool_size;it++)
20✔
725
        {
726
                unsigned j = std::uniform_int_distribution<unsigned>(0,your_decks.size()-1)(re);
19✔
727
                unsigned i = std::uniform_int_distribution<unsigned>(0,your_decks.size()-1)(re);
19✔
728
                Deck* nxt = your_decks[j]->clone();
19✔
729
                if(std::uniform_int_distribution<unsigned>(0,1)(re))
19✔
730
                {
731
                        mutate(your_decks[i],nxt,all_candidates,re,best_gap,evaluated_decks
12✔
732
#ifndef NQUEST
733
                                        , quest
734
#endif
735
            );
736
                }
737
                else
738
                {
739
                        crossover(your_decks[i],your_decks[j],nxt,re,best_gap,evaluated_decks
7✔
740
#ifndef NQUEST
741
                                        , quest
742
#endif
743
            );
744
                }
745
                your_decks.push_back(nxt);
19✔
746
        }
747
        //sim pool
748
        for( auto i_deck :your_decks)
21✔
749
        {
750
                copy_deck(i_deck,cur_deck);
20✔
751
                cur_score = fitness(cur_deck, best_score, evaluated_decks, zero_results, skipped_simulations, proc,true);
20✔
752
                pool.push_back(std::make_pair(i_deck,cur_score));
20✔
753
                if(cur_score.points > best_score.points)
20✔
754
                {
755
                        best_score = cur_score;
3✔
756
                        best_deck = cur_deck->clone();
3✔
757
                        best_gap = check_requirement(cur_deck, requirement
3✔
758
#ifndef NQUEST
759
                                        , quest
760
#endif
761
                                        );
762
                        std::cout << "Deck improved: " << best_deck->hash() <<":";
6✔
763
                        print_deck_inline(get_deck_cost(best_deck), best_score, best_deck);
3✔
764
                }
765
        }
766

767
        for( unsigned gen= 0; gen< generations && !is_timeout_reached() ;gen++ )
51✔
768
        {
769
                std::cout << "GENERATION: " << gen << std::endl;
50✔
770

771
                //sort
772
                auto sort = [](std::pair<Deck*,FinalResults<long double>> l,std::pair<Deck*,FinalResults<long double>> r) {return l.second.points > r.second.points;};
4,787✔
773
                std::sort(pool.begin(),pool.end(),sort);
50✔
774
                //breed
775
                //cross
776
                for ( unsigned it = 0; it < pool_cross;it++)
350✔
777
                {
778
                        unsigned i = std::uniform_int_distribution<unsigned>(0,pool_keep-1)(re);
300✔
779
                        unsigned j = std::uniform_int_distribution<unsigned>(pool_keep,pool_size-pool_mutate)(re);
300✔
780
                        //unsigned  k= std::uniform_int_distribution<unsigned>(0,pool_size-pool_mutate)(re);
781
                        unsigned k = -1;
300✔
782
                        while (k >= pool_size-pool_mutate)
629✔
783
                                k=std::geometric_distribution<unsigned>(0.2)(re); //prefer crossover with strong decks
329✔
784
                        crossover(pool[i].first,pool[k].first,pool[j].first,re,best_gap, evaluated_decks
300✔
785
#ifndef NQUEST
786
                                        , quest
787
#endif
788
            );
789
                        //crossover(pool[it+pool_size/4*2].first,pool[it+pool_size/4*3].first,pool[it+pool_size/4*3].first,re,best_gap, evaluated_decks);
790
                        //crossover(pool[it].first,pool[(it+pool_size/8)%(pool_size/4)].first,pool[it+pool_size/4*2].first,re,best_gap, evaluated_decks);
791
                        //mutate(pool[it].first,pool[it+pool_size/4*3].first,all_candidates,re,best_gap, evaluated_decks);
792
                }
793
                //mutate pool_keep to replace lowest scores
794
                for ( unsigned it = pool_size-pool_mutate; it < pool_size;it++)
450✔
795
                {
796
                        unsigned i = std::uniform_int_distribution<unsigned>(0,pool_keep-1)(re);
400✔
797
                        //unsigned j = std::uniform_int_distribution<unsigned>(pool_keep,pool_size-1)(re);
798
                        mutate(pool[i].first,pool[it].first,all_candidates,re,best_gap, evaluated_decks
400✔
799
#ifndef NQUEST
800
                                        , quest
801
#endif
802
            );
803
                }
804
                //mutate duplicates
805
                for ( unsigned it = 0; it < pool_size;it++)
1,050✔
806
                {
807
                        for (unsigned i = it+1; i < pool_size;i++)
10,500✔
808
                        {
809
                                if(pool[it].first->alpha_dominion && pool[i].first->alpha_dominion && pool[it].first->hash().substr(8)==pool[i].first->hash().substr(8)) //ignore commander + dominion
47,500✔
810
                                {
811
                                        mutate(pool[i].first->clone(),pool[i].first,all_candidates,re,best_gap, evaluated_decks
267✔
812
#ifndef NQUEST
813
                                        , quest
814
#endif
815
                    );
816

817
                                        FinalResults<long double> nil{0, 0, 0, 0, 0, 0, 1};
267✔
818
                                        pool[i].second = nil; //lowest score approx Null
267✔
819
                                }
820
                        }
821
                }
822
                //calc fitness
823
                for (unsigned it = pool_keep; it < pool_size; it++)
750✔
824
                {
825
                        copy_deck(pool[it].first,cur_deck);
700✔
826
                        cur_score = fitness(cur_deck, best_score, evaluated_decks, zero_results, skipped_simulations, proc,true);
700✔
827
                        pool[it].second = cur_score;
700✔
828
                        if(cur_score.points > best_score.points)
700✔
829
                        {
830
                                best_score = cur_score;
9✔
831
                                best_deck = cur_deck->clone();
9✔
832
                                best_gap = check_requirement(cur_deck, requirement
9✔
833
#ifndef NQUEST
834
                                                , quest
835
#endif
836
                                                );
837
                                if(it < pool_size-pool_mutate)
9✔
838
                                {
839
                                        if (debug_print >= 0)
6✔
840
                                                std::cout << "Crossover: " <<std::endl;
6✔
841
                                        std::cout << "Deck improved: " << best_deck->hash() <<":";
18✔
842
                                }
843
                                else
844
                                {
845
                                        if (debug_print >= 0)
3✔
846
                                                std::cout << "Mutation: " <<std::endl;
3✔
847
                                        std::cout << "Deck improved: " << best_deck->hash() <<":";
9✔
848
                                }
849

850
                                print_deck_inline(get_deck_cost(best_deck), best_score, best_deck);
9✔
851
                        }
852
                }
853
#ifndef NDEBUG
854
                if (debug_print >= 0)
50✔
855
                {
856
                        std::cout << "---------------POOL---------------" << std::endl;
50✔
857
                        for (unsigned it =0; it < pool.size();++it)
1,050✔
858
                        {
859
                                if(it==0)std::cout << "---------------KEEP---------------" << std::endl;
1,000✔
860
                                if(it==pool_keep)std::cout << "---------------CROSS--------------" << std::endl;
1,000✔
861
                                if(it==pool_keep+pool_cross)std::cout << "---------------MUTATE-------------" << std::endl;
1,000✔
862
                                auto a = pool[it];
1,000✔
863
                                print_deck_inline(get_deck_cost(a.first),a.second,a.first);
1,000✔
864
                        }
865
                        std::cout << "---------------PEND---------------" << std::endl;
50✔
866
                }
867
#endif
868
        }
869

870
        unsigned simulations = 0;
1✔
871
        for (auto evaluation: evaluated_decks)
1,123✔
872
        { simulations += evaluation.second.second; }
1,122✔
873
        std::cout << "Evaluated " << evaluated_decks.size() << " decks (" << simulations << " + " << skipped_simulations << " simulations)." << std::endl;
1✔
874
        print_sim_card_values(best_deck,proc,num_iterations);
1✔
875
        std::cout << "Optimized Deck: ";
1✔
876
        print_deck_inline(get_deck_cost(best_deck), best_score, best_deck,true);
1✔
877
        print_upgraded_cards(best_deck);
1✔
878
        return std::make_pair(best_deck->clone(),best_score);
2✔
879
}
2✔
880

881

882
unsigned factorial(unsigned n)
×
883
{
884
        unsigned retval = 1;
×
UNCOV
885
        for (int i = n; i > 1; --i)
×
UNCOV
886
                retval *= i;
×
UNCOV
887
        return retval;
×
888
}
889

890
void recursion(unsigned num_iterations, std::vector<unsigned> used, unsigned pool, std::vector<const Card*> forts,Process&proc, FinalResults<long double>& best_score,std::vector<const Card*> & best_forts,std::unordered_map<std::string,EvaluatedResults> & evaluated_decks, EvaluatedResults& zero_results, unsigned long& skipped_simulations)
×
891
{
892
        if(used.size()==pool)
×
893
        {
UNCOV
894
                for(auto your_deck : proc.your_decks)
×
895
                {
UNCOV
896
                        your_deck->fortress_cards.clear();
×
897
                        for(unsigned i = 0; i < pool;++i)
×
898
                        {
UNCOV
899
                                your_deck->fortress_cards.emplace_back(forts[used[i]]);
×
900
                        }
901
                }
902
                //sim
903
                std::stringstream ios;
×
904
                encode_deck_ext_b64(ios,proc.your_decks[0]->fortress_cards);
×
905
                auto hash = ios.str();
×
906
                auto && emplace_rv = evaluated_decks.insert({hash,zero_results});
×
907
                auto & prev_results = emplace_rv.first->second;
×
908
                if(!emplace_rv.second)
×
909
                {
910
                        skipped_simulations += skipped_simulations_increment(proc, prev_results.second);
×
911
                }
912
                auto compare_results= proc.evaluate(num_iterations, prev_results);
×
UNCOV
913
                auto current_score = compute_score(compare_results, proc.factors);
×
914

915
                if(current_score.points > best_score.points+min_increment_of_score) {
×
UNCOV
916
                        best_score = current_score;
×
917
                        std::vector<const Card*> copy_forts(proc.your_decks[0]->fortress_cards);
×
UNCOV
918
                        best_forts = copy_forts;
×
919
                        std::cout << "Forts improved: " << hash << " : ";
×
920
                        print_score_info(compare_results, proc.factors);
×
921
                        print_score_inline(best_score);
×
922
                        std::cout << ": ";
×
UNCOV
923
                        print_cards_inline(best_forts);
×
UNCOV
924
                }
×
925
                used.clear();
×
UNCOV
926
                used.shrink_to_fit();
×
927
        }
×
928
        else
929
        {
930
                for(unsigned i =0;i < forts.size();++i)
×
931
                {
UNCOV
932
                        if(std::find(used.begin(),used.end(),i)==used.end()) //not contained
×
933
                        {
UNCOV
934
                                std::vector<unsigned> tmp_used (used);
×
935
                                tmp_used.emplace_back(i);
×
936
                                recursion(num_iterations,tmp_used,pool,forts,proc,best_score,best_forts,evaluated_decks,zero_results,skipped_simulations);
×
937
                        }
×
938
                }
939
        }
940
}
×
941

942
DeckResults forts_climbing(unsigned num_iterations, Process& proc) {
×
943
        EvaluatedResults zero_results = { EvaluatedResults::first_type(proc.enemy_decks.size()*proc.your_decks.size()), 0 };
×
944
        unsigned pool = std::get<0>(proc.your_decks[0]->variable_forts[0]);
×
945
        std::vector<const Card*> forts(std::get<2>(proc.your_decks[0]->variable_forts[0]));
×
946
        for(unsigned i =0; i < proc.your_decks.size();++i)
×
947
        {
948
                proc.your_decks[i]->variable_forts.clear();
×
949
        }
950
        std::vector<unsigned> used;
×
951
        used.reserve(pool);
×
952
        std::vector<const Card*> best_forts{pool};
×
953
        FinalResults<long double> best_score{0, 0, 0, 0, 0, 0, 0};
×
UNCOV
954
        unsigned long skipped_simulations{0};
×
UNCOV
955
        std::unordered_map<std::string,EvaluatedResults> evaluated_decks{{"",zero_results}};
×
UNCOV
956
        recursion(num_iterations,used,pool,forts, proc,best_score,best_forts,evaluated_decks,zero_results,skipped_simulations);
×
UNCOV
957
        unsigned simulations = 0;
×
UNCOV
958
        for (auto evaluation: evaluated_decks)
×
UNCOV
959
        { simulations += evaluation.second.second; }
×
UNCOV
960
        std::cout << "Evaluated " << evaluated_decks.size() << " decks (" << simulations << " + " << skipped_simulations << " simulations)." << std::endl;
×
UNCOV
961
        std::cout << "Optimized Deck: ";
×
UNCOV
962
        print_cards_inline(best_forts);
×
UNCOV
963
        Deck* tmp = proc.your_decks[0]->clone();
×
UNCOV
964
        tmp->commander = nullptr;
×
UNCOV
965
        tmp->alpha_dominion = nullptr;
×
UNCOV
966
        tmp->cards = best_forts; // return forts
×
UNCOV
967
        return std::make_pair(tmp,best_score);
×
UNCOV
968
}
×
969

970
inline bool contains(std::multimap<FinalResults<long double>, Deck*, std::greater<FinalResults<long double>>> best,Deck* d)
62✔
971
{
972
                for(auto it = best.begin();it!=best.end();it++)
314✔
973
                {
974
                        if(it->second->hash().compare(d->hash())==0) return true;
314✔
975
                }
976
                return false;
977
}
978

979
inline void check_and_update(std::multimap<FinalResults<long double>, Deck*, std::greater<FinalResults<long double>>>& best,Deck* cur_deck,bool & deck_has_been_improved, FinalResults<long double>& best_score )
47✔
980
{
981
        auto tmp_result = (best.size()<pool_size)?0.:(std::next(best.begin(),pool_size))->first.points;
47✔
982
        //std::cout << "IMP CMD" << std::endl;
983
        if(best_score.points > tmp_result && !contains(best,cur_deck))
141✔
984
        {
985
                        deck_has_been_improved = true;
43✔
986
                        if(best_score > best.begin()->first){
43✔
987
                                        std::cout << "Deck improved: " << cur_deck->hash() <<":" << std::endl;
18✔
988
                                        print_deck_inline(get_deck_cost(cur_deck), best_score, cur_deck);
9✔
989
                        }
990
                        best.insert(std::make_pair(best_score,cur_deck->clone()));
43✔
991
                        if(best.size()==pool_size+1)best.erase(std::prev(best.end(),1));
43✔
992
        }
993
}
47✔
994

995
DeckResults beam_climb(unsigned num_min_iterations, unsigned num_iterations, std::vector<Deck*> your_decks, Process& proc, Requirement & requirement
3✔
996
#ifndef NQUEST
997
                , Quest & quest
998
#endif
999
                )
1000
{
1001
        if(pool_size==0){
3✔
1002
                        pool_size = min_beam_size;
3✔
1003
        }
1004

1005
        //your_decks.size();
1006
        //std::vector<std::pair<Deck*,FinalResults<long double>>> pool;
1007

1008
        //auto sort = [](FinalResults<long double> l,FinalResults<long double> r) {return l.points > r.points;};
1009
        std::multimap<FinalResults<long double>, Deck*, std::greater<FinalResults<long double>>> best;
3✔
1010
        Deck* cur_deck = proc.your_decks[0];
3✔
1011

1012

1013
        EvaluatedResults zero_results = { EvaluatedResults::first_type(proc.enemy_decks.size()), 0 };
3✔
1014

1015
        std::unordered_map<std::string, EvaluatedResults> evaluated_decks{{cur_deck->hash(), zero_results}};
12✔
1016
        EvaluatedResults& results = proc.evaluate(num_min_iterations, evaluated_decks.begin()->second);
3✔
1017
        print_score_info(results, proc.factors);
3✔
1018
        FinalResults<long double> best_score = compute_score(results, proc.factors); //init sim, todo remove
3✔
1019

1020
        unsigned deck_cost = get_deck_cost(cur_deck);
3✔
1021
        fund = std::max(fund, deck_cost);
3✔
1022
        print_deck_inline(deck_cost, best_score, cur_deck,true);
3✔
1023
        std::mt19937& re = proc.threads_data[0]->re;
3✔
1024
        unsigned cur_gap = check_requirement(cur_deck, requirement
3✔
1025
#ifndef NQUEST
1026
                        , quest
1027
#endif
1028
                        );
3✔
1029

1030
        unsigned long skipped_simulations = 0;
3✔
1031

1032
        bool is_random = (cur_deck->strategy == DeckStrategy::random) || (cur_deck->strategy == DeckStrategy::flexible);
3✔
1033
        bool deck_has_been_improved = true;
3✔
1034

1035
        std::vector<const Card*> commander_candidates;
3✔
1036
        std::vector<const Card*> alpha_dominion_candidates;
3✔
1037
        std::vector<const Card*> card_candidates;
3✔
1038

1039
        auto mixed_candidates = get_candidate_lists(proc);
3✔
1040
        commander_candidates = mixed_candidates.at(0);
3✔
1041
        alpha_dominion_candidates = mixed_candidates.at(1);
3✔
1042
        card_candidates = mixed_candidates.at(2);
3✔
1043
        // add current alpha dominion to candidates if necessary
1044
        // or setup first candidate into the deck if no alpha dominion defined
1045
        if (use_owned_dominions)
3✔
1046
        {
1047
                for(auto i_deck : your_decks) //add all alpha doms
6✔
1048
                {
1049
                        if (i_deck->alpha_dominion)
3✔
1050
                        {
UNCOV
1051
                                if (!std::count(alpha_dominion_candidates.begin(), alpha_dominion_candidates.end(),i_deck->alpha_dominion))
×
1052
                                {
UNCOV
1053
                                        alpha_dominion_candidates.emplace_back(i_deck->alpha_dominion);
×
1054
                                }
1055
                        }
1056
                }
1057
                if (debug_print > 0)
3✔
1058
                {
UNCOV
1059
                        for (const Card* dom_card : alpha_dominion_candidates)
×
1060
                        {
UNCOV
1061
                                std::cout << " ** next Alpha Dominion candidate: " << dom_card->m_name
×
UNCOV
1062
                                        << " ($: " << alpha_dominion_cost(dom_card) << ")" << std::endl;
×
1063
                        }
1064
                }
1065
        }
1066

1067
        //Deck* best_deck = cur_deck->clone();
1068
        //FinalResults<long double> cur_score = best_score;
1069
        //unsigned best_gap = cur_gap;
1070

1071

1072

1073
        //if(your_decks.size()>pool_size) your_decks.resize(pool_size);
1074
        Deck * best_deck = cur_deck->clone();
3✔
1075
        auto best_decks = your_decks;
3✔
1076
        std::string best_hash = cur_deck->hash();
3✔
1077
        unsigned from_slot;
3✔
1078
        unsigned count_slot=0; //count iterations
3✔
1079
        unsigned dead_slot=0; //last deck improvement
3✔
1080
        unsigned mod_permute = 10*9*8*7*6*5*4*3*2*1;
3✔
1081
        //FinalResults<long double> tmp_result;
1082
        FinalResults<long double> nil{0, 0, 0, 0, 0, 0, num_min_iterations};
3✔
1083
        //sim passed decks
1084
        for(auto ii : your_decks){
6✔
1085
                        auto tdeck = ii->clone();
3✔
1086
                        copy_deck(tdeck,cur_deck);
3✔
1087
                        auto tscore = fitness(cur_deck,nil,evaluated_decks,zero_results,skipped_simulations,proc);
3✔
1088
                        if(!contains(best,cur_deck))best.insert(std::make_pair(tscore,tdeck));
3✔
1089
                        if(best.size()==pool_size+1)best.erase(std::prev(best.end(),1));
3✔
1090
        }
1091
        //fill remaining
1092
        while(best.size()<pool_size){
15✔
1093
                unsigned j = std::uniform_int_distribution<unsigned>(0,your_decks.size()-1)(re);
12✔
1094
                unsigned i = std::uniform_int_distribution<unsigned>(0,your_decks.size()-1)(re);
12✔
1095
                Deck* nxt = your_decks[j]->clone();
12✔
1096
                mutate(your_decks[i],nxt,card_candidates,re,cur_gap,evaluated_decks
12✔
1097
#ifndef NQUEST
1098
                                        , quest
1099
#endif
1100
        ); //no crossovers here only fill with mutations
1101
                copy_deck(nxt,cur_deck);
12✔
1102
                auto tscore = fitness(cur_deck,nil,evaluated_decks,zero_results,skipped_simulations,proc);
12✔
1103
                if(!contains(best,nxt)){best.insert(std::make_pair(tscore,nxt));}
12✔
1104
        }
1105

1106
        while(true)
38✔
1107
        {
1108

1109
#ifndef NDEBUG
1110
                if (debug_print >= 0)
38✔
1111
                {
1112
                        std::cout << "---------------BEST---------------" << std::endl;
38✔
1113
                        for (auto it = best.begin(); it != best.end();++it)
228✔
1114
                        {
1115
                                print_deck_inline(get_deck_cost(it->second),it->first,it->second);
190✔
1116
                        }
1117
                        std::cout << "---------------BEND---------------" << std::endl;
38✔
1118
                }
1119
#endif
1120
                count_slot = (count_slot+1)%(mod_permute); //TODO Modulo here? % 10*9*8*7*6*5*4*3*2*1
38✔
1121
                if(is_timeout_reached()){break;}
38✔
1122
                if (deck_has_been_improved)
38✔
1123
                {
1124
                        dead_slot = count_slot;
8✔
1125
                        deck_has_been_improved = false;
8✔
1126
                }
1127
                else if((dead_slot<count_slot && count_slot-dead_slot>=10 )||(count_slot<dead_slot && count_slot+mod_permute-dead_slot>=10 )) // nothing improved for 10 cycles
30✔
1128
                {
1129
                        break; // done climbing
1130
                        //TODO climbex like beam climb here
1131
                }
1132

1133
                // get new best decks:
1134
                best_decks.clear();
35✔
1135
                for(auto it = best.begin();it!=best.end();it++)
210✔
1136
                {
1137
                        best_decks.push_back(it->second);
175✔
1138
                }
1139
                //sim deck
1140
                for( auto i_deck :best_decks)
210✔
1141
                {
1142
                        copy_deck(i_deck->clone(),cur_deck);
175✔
1143
                        best_deck = i_deck->clone();
175✔
1144
                        //copy_deck(i_deck,best_deck);
1145
                        best_score = fitness(cur_deck,nil,evaluated_decks,zero_results,skipped_simulations,proc); // grab from stored results or sim it
175✔
1146
                        from_slot = std::max(freezed_cards, (count_slot) % std::min<unsigned>(max_deck_len, best_deck->cards.size() + 1));
241✔
1147
                        //climb + save best ones to best
1148

1149
                        // commander
1150
                if (requirement.num_cards.count(best_deck->commander) == 0)
175✔
1151
                {
1152
                        // << commander candidate loop >>
1153
                        for (const Card* commander_candidate: commander_candidates)
413✔
1154
                        {
1155
                                if (best_score.points - target_score > -1e-9)
387✔
1156
                                { break; }
1157
                                if (commander_candidate == best_deck->commander)
238✔
1158
                                {continue;}
23✔
1159
                                //std::cout << "TRY CMD" << std::endl;
1160
                                if(try_improve_deck(cur_deck, -1, -1, commander_candidate,
215✔
1161
                                                best_deck->commander, best_deck->alpha_dominion, best_deck->cards, best_score, cur_gap, best_hash,
215✔
1162
                                                evaluated_decks, zero_results, skipped_simulations, proc,false
1163
#ifndef NQUEST
1164
                        , quest
1165
#endif
1166
                        ))
1167
                                                {
1168
                                                                check_and_update(best,cur_deck,deck_has_been_improved,best_score);
10✔
1169
                                                }
1170
                        }
1171
                        copy_deck(best_deck,cur_deck);
175✔
1172
                }
1173

1174
                // alpha dominion
1175
                if (use_owned_dominions && !alpha_dominion_candidates.empty())
175✔
1176
                {
1177
                        // << alpha dominion candidate loop >>
1178
                        for (const Card* alpha_dominion_candidate: alpha_dominion_candidates)
253✔
1179
                        {
1180
                                if (best_score.points - target_score > -1e-9)
227✔
1181
                                { break; }
1182
                                if (alpha_dominion_candidate == best_deck->alpha_dominion)
78✔
1183
                                { continue; }
21✔
1184
                                if(try_improve_deck(cur_deck, -1, -1, alpha_dominion_candidate,
57✔
1185
                                                best_deck->commander, best_deck->alpha_dominion, best_deck->cards, best_score, cur_gap, best_hash,
57✔
1186
                                                evaluated_decks, zero_results, skipped_simulations, proc,false
1187
#ifndef NQUEST
1188
                        , quest
1189
#endif
1190
                        ))
1191
                                                {
1192
                                                                check_and_update(best,cur_deck,deck_has_been_improved,best_score);
8✔
1193
                                                }
1194
                        }
1195
                        copy_deck(best_deck,cur_deck);
175✔
1196
                }
1197

1198
                // shuffle candidates
1199
                std::shuffle(card_candidates.begin(), card_candidates.end(), re);
175✔
1200

1201
                // << card candidate loop >>
1202
                //for (const Card* card_candidate: card_candidates)
1203
                for (auto it = card_candidates.begin(); it != card_candidates.end();++it)
749✔
1204
                {
1205
                        const Card* card_candidate = *it;
730✔
1206
                        for (unsigned to_slot(is_random ? from_slot : card_candidate ? freezed_cards : (best_deck->cards.size() - 1));
1,460✔
1207
                                        to_slot < (is_random ? (from_slot + 1) : (best_deck->cards.size() + (from_slot < best_deck->cards.size() ? 0 : 1)));
1,460✔
1208
                                        ++ to_slot)
1209
                        {
1210
                                if (card_candidate ?
730✔
1211
                                                (from_slot < best_deck->cards.size() && (from_slot == to_slot && card_candidate == best_deck->cards[to_slot])) // 2 Omega -> 2 Omega
692✔
1212
                                                :
1213
                                                (from_slot == best_deck->cards.size())) // void -> void
38✔
1214
                                { continue; }
22✔
1215

1216
                                //std::cout << "TRY" << std::endl;
1217
                                //print_deck_inline(get_deck_cost(best_deck),best_score,best_deck);
1218
                                if(try_improve_deck(cur_deck, from_slot, to_slot, card_candidate,
708✔
1219
                                                best_deck->commander, best_deck->alpha_dominion, best_deck->cards, best_score, cur_gap, best_hash,
708✔
1220
                                                evaluated_decks, zero_results, skipped_simulations, proc,false
1221
#ifndef NQUEST
1222
                        , quest
1223
#endif
1224
                        ))
1225
                                                {
1226
                                                                check_and_update(best,cur_deck,deck_has_been_improved,best_score);
29✔
1227
                                                }
1228
                                //print_deck_inline(get_deck_cost(best_deck),best_score,best_deck);
1229
                        }
1230
                        if (best_score.points - target_score > -1e-9)
730✔
1231
                        { break; }
1232

1233
                        }
1234
                }
1235
}
1236

1237
        best_deck = best.begin()->second;
3✔
1238
        best_score = best.begin()->first;
3✔
1239

1240
        unsigned simulations = 0;
3✔
1241
        for (auto evaluation: evaluated_decks)
1,075✔
1242
        { simulations += evaluation.second.second; }
1,072✔
1243
        std::cout << "Evaluated " << evaluated_decks.size() << " decks (" << simulations << " + " << skipped_simulations << " simulations)." << std::endl;
3✔
1244
        print_sim_card_values(best_deck,proc,num_iterations);
3✔
1245
        std::cout << "Optimized Deck: ";
3✔
1246
        print_deck_inline(get_deck_cost(best_deck), best_score, best_deck,true);
3✔
1247
        print_upgraded_cards(best_deck);
3✔
1248
        return std::make_pair(best_deck->clone(),best_score);
3✔
1249
}
6✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc